VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "clsCharacter"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Private Const m_cNAME As String = "clsCharacter"

Private m_oTableHelperCharacter As New clsFOTAADOTableHelper

'Unique identifiers for the character.  Use GUID if at all possible.
Public nCharID As Long
Public sGUID As String

'Constants used for serializing and deserializing this character
Private Const m_cStatSep As String = "[S ¶]"
Private Const m_cNotAStat As String = "[NotAStat]"
Private Const m_cTempItemSep As String = "[TI ¼]"

'Our spells, and our font choices
Public m_oSpells As New clsSpells
Public m_oFont As StdFont
Public m_nFontColor As Long
'Our items
Public m_oItems As New clsItems
'Our loaded macros
Public m_oMacros As New clsMacros

'Vitals
Public sName As String, nHP As Long, nMaxHP As Long, nSP As Long, nMaxSP As Long
Public nExperience1 As Long, nLevel1 As Long, nExpForNextLevel1 As Long, nExperience2 As Long, nLevel2 As Long, nExpForNextLevel2 As Long
'Stats
Public nStr As Long, sExceptionalStr As String, nDex As Long, nCon As Long, nInt As Long, nWis As Long, nChr As Long, nCom As Long, nPer As Long
'Visual
Public nPhysicialAge As Long, nActualAge As Long, sRace As String, nHeight As Long, nWeight As Long, sMarks As String, sEyeColor As String, sHairColor As String
'Saves
Public nDeath As Long, nRod As Long, nPoly As Long, nBreath As Long, nSpell As Long
'Combat
Public nTHACOBonus As Long
'Misc
Public sNotes As String, sPicture As String
'sClass1 As String, sClass2 As String,
Public sSex As String, sAlignment As String
'Custom Class Info
'Public sCustomLevels As String, sCustomSaves As String, sCustomTHACO As String
'Abilities
' For Weapon 1
Public nCombatNumDice1 As Long, nCombatDice1 As Long, nCombatDamagePlus1 As Long, nCombatTHACOBonus1 As Long, nCombatNumOfAttacks1 As Long, nCombatNumOfAttacksEvery1 As Long
' For Weapon 2
Public nCombatNumDice2 As Long, nCombatDice2 As Long, nCombatDamagePlus2 As Long, nCombatTHACOBonus2 As Long, nCombatNumOfAttacks2 As Long, nCombatNumOfAttacksEvery2 As Long
Public nCombatACBonus As Long, nCombatInitModifier As Long
'No longer minion stat only.
Private sAttackText1 As String, sAttackText2 As String
'Minion Only Stats
Public bCombatAIOn As Boolean, bWillAttackParty As Boolean, bRangedCombat As Boolean
Public nTargetChoice As Long
Public nOwnerNumber As Long
'BattleMap Stats
Public nX As Long, nY As Long, nDirection As Long '0 = north, 1 = East....
Public sCondition As String 'This determines which picutre we show for action
Public sState As String 'This determines if the token is on the board or not
'New Class ID's
Public nClassID1 As Long
Public nClassID2 As Long
'Proficiency Info
Public sProficiencies As String
Public sLanguages As String
'WeaponID's if an item is being used for combat
Public nWeaponID1 As Long
Public nWeaponID2 As Long
'New damage bonus for the character
Public nCombatDamageBonus As Long
Public bLoadedLastTime As Boolean
'Non-Weapon Proficiencies
Public sWeapProf As String
'MR
Public nMR As Long

'This is a minion only field for hiding chars on the minion and chat room screens
Public bShowToUsers As Boolean
Public bCanLootMinion As Boolean
Public bGoesAwayWhenLooted As Boolean

'08/04/2004 Chris Hill  This lets us ensure that the same combat round is not repeatedly used
Public nLastRoundAttackedFor As Long

Public Function Name() As String
  
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = "clsCharacter.Name"
    'Call WriteProcStart(sRoutineName)
    
    
    Name = "clsCharacter"


    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Sub Delete(dbDND As ADODB.Connection)
  
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = "clsCharacter.Delete"
    'Call WriteProcStart(sRoutineName)
    
    
    Dim rstChar As ADODB.Recordset
    
    Set rstChar = OpenMyRecordset(dbDND, "SELECT * FROM CharacterInfo WHERE CharacterID = " & nCharID)
    If rstChar.RecordCount > 0 Then
        Call dbDND.Execute("DELETE * FROM CharacterInfo WHERE CharacterID = " & nCharID)
    End If


    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Sub

Public Function TurnUndead() As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".TurnUndead"
    Call WriteProcStart(sRoutineName)


    Dim nHD As Long
    Dim oTableTurnUndead As clsTable
    Dim nRolled As Long
    Dim nTurnRate1 As Long, nTurnRate2 As Long, nTurnRate As Long
    
    'Find our turn undead success
    Set oTableTurnUndead = g_oTables.GetTableByNode("Turn Undead")
    nTurnRate1 = oTableTurnUndead.FindColumn(nLevel1)
    nTurnRate2 = oTableTurnUndead.FindColumn(nLevel2)
    nTurnRate = Max(nTurnRate1, nTurnRate2)
    nRolled = Random(1, 20)
    
    'Found our column!
    If nLevel1 >= 1 And nLevel2 > -1 Then
        For nHD = 2 To oTableTurnUndead.Rows()
            If IsNumeric(oTableTurnUndead.Data(nTurnRate, nHD)) = True Then
                If oTableTurnUndead.Data(nTurnRate, nHD) > nRolled Then
                    nHD = nHD - 1
                    Exit For
                End If
            End If
        Next nHD
        TurnUndead = "Turned " & Random(2, 12) & " creatures, " & oTableTurnUndead.Data(1, nHD) & " level or weaker"
    Else
        TurnUndead = "Unable to locate characters level <" & nLevel1 & "," & nLevel2 & "> on the turn undead table"
    End If


    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

Public Function Item(sStatName As String, Optional vNewVal As Variant = "<Nothing>") As Variant

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".Item"
    Call WriteProcStart(sRoutineName)


    'Vitals
    If UCase(sStatName) = UCase("sName") Then
        If Not vNewVal = "<Nothing>" Then sName = vNewVal
        Item = sName
    ElseIf UCase(sStatName) = UCase("nHP") Then
        If Not vNewVal = "<Nothing>" Then nHP = vNewVal
        Item = nHP
    ElseIf UCase(sStatName) = UCase("nMaxHP") Then
        If Not vNewVal = "<Nothing>" Then nMaxHP = vNewVal
        Item = nMaxHP
    ElseIf UCase(sStatName) = UCase("nSP") Then
        If Not vNewVal = "<Nothing>" Then nSP = vNewVal
        Item = nSP
    ElseIf UCase(sStatName) = UCase("nMaxSP") Then
        If Not vNewVal = "<Nothing>" Then nMaxSP = vNewVal
        Item = nMaxSP
    ElseIf UCase(sStatName) = UCase("nExperience1") Then
        If Not vNewVal = "<Nothing>" Then nExperience1 = vNewVal
        Item = nExperience1
    ElseIf UCase(sStatName) = UCase("nLevel1") Or _
           UCase(sStatName) = UCase("nLevel") Then
        If Not vNewVal = "<Nothing>" Then nLevel1 = vNewVal
        Item = nLevel1
    ElseIf UCase(sStatName) = UCase("nExpForNextLevel1") Then
        If Not vNewVal = "<Nothing>" Then nExpForNextLevel1 = vNewVal
        Item = nExpForNextLevel1
    ElseIf UCase(sStatName) = UCase("nExperience2") Then
        If Not vNewVal = "<Nothing>" Then nExperience2 = vNewVal
        Item = nExperience2
    ElseIf UCase(sStatName) = UCase("nLevel2") Then
        If Not vNewVal = "<Nothing>" Then nLevel2 = vNewVal
        Item = nLevel2
    ElseIf UCase(sStatName) = UCase("nExpForNextLevel2") Then
        If Not vNewVal = "<Nothing>" Then nExpForNextLevel2 = vNewVal
        Item = nExpForNextLevel2

    'Visual
    ElseIf UCase(sStatName) = UCase("nPhysicialAge") Then
        If Not vNewVal = "<Nothing>" Then nPhysicialAge = vNewVal
        Item = nPhysicialAge
    ElseIf UCase(sStatName) = UCase("nActualAge") Then
        If Not vNewVal = "<Nothing>" Then nActualAge = vNewVal
        Item = nActualAge
    ElseIf UCase(sStatName) = UCase("sRace") Then
        If Not vNewVal = "<Nothing>" Then sRace = vNewVal
        Item = sRace
    ElseIf UCase(sStatName) = UCase("nHeight") Then
        If Not vNewVal = "<Nothing>" Then nHeight = vNewVal
        Item = nHeight
    ElseIf UCase(sStatName) = UCase("nWeight") Then
        If Not vNewVal = "<Nothing>" Then nWeight = vNewVal
        Item = nWeight
    ElseIf UCase(sStatName) = UCase("sMarks") Then
        If Not vNewVal = "<Nothing>" Then sMarks = vNewVal
        Item = sMarks
    ElseIf UCase(sStatName) = UCase("sEyeColor") Then
        If Not vNewVal = "<Nothing>" Then sEyeColor = vNewVal
        Item = sEyeColor
    ElseIf UCase(sStatName) = UCase("sHairColor") Then
        If Not vNewVal = "<Nothing>" Then sHairColor = vNewVal
        Item = sHairColor

    'Stats
    ElseIf UCase(sStatName) = UCase("nStr") Then
        If Not vNewVal = "<Nothing>" Then nStr = vNewVal
        Item = nStr
    ElseIf UCase(sStatName) = UCase("sExceptionalStr") Then
        If Not vNewVal = "<Nothing>" Then sExceptionalStr = vNewVal
        Item = sExceptionalStr
    ElseIf UCase(sStatName) = UCase("nDex") Then
        If Not vNewVal = "<Nothing>" Then nDex = vNewVal
        Item = nDex
    ElseIf UCase(sStatName) = UCase("nCon") Then
        If Not vNewVal = "<Nothing>" Then nCon = vNewVal
        Item = nCon
    ElseIf UCase(sStatName) = UCase("nInt") Then
        If Not vNewVal = "<Nothing>" Then nInt = vNewVal
        Item = nInt
    ElseIf UCase(sStatName) = UCase("nWis") Then
        If Not vNewVal = "<Nothing>" Then nWis = vNewVal
        Item = nWis
    ElseIf UCase(sStatName) = UCase("nChr") Then
        If Not vNewVal = "<Nothing>" Then nChr = vNewVal
        Item = nChr
    ElseIf UCase(sStatName) = UCase("nCom") Then
        If Not vNewVal = "<Nothing>" Then nCom = vNewVal
        Item = nCom
    ElseIf UCase(sStatName) = UCase("nPer") Then
        If Not vNewVal = "<Nothing>" Then nPer = vNewVal
        Item = nPer
    
    'Saves
    ElseIf UCase(sStatName) = UCase("nDeath") Then
        If Not vNewVal = "<Nothing>" Then nDeath = vNewVal
        Item = nDeath
    ElseIf UCase(sStatName) = UCase("nRod") Then
        If Not vNewVal = "<Nothing>" Then nRod = vNewVal
        Item = nRod
    ElseIf UCase(sStatName) = UCase("nPoly") Then
        If Not vNewVal = "<Nothing>" Then nPoly = vNewVal
        Item = nPoly
    ElseIf UCase(sStatName) = UCase("nBreath") Then
        If Not vNewVal = "<Nothing>" Then nBreath = vNewVal
        Item = nBreath
    ElseIf UCase(sStatName) = UCase("nSpell") Then
        If Not vNewVal = "<Nothing>" Then nSpell = vNewVal
        Item = nSpell
        
    'Combat
    ElseIf UCase(sStatName) = UCase("nTHACOBonus") Then
        If Not vNewVal = "<Nothing>" Then nTHACOBonus = vNewVal
        Item = nTHACOBonus
        
    'Misc
    ElseIf UCase(sStatName) = UCase("sNotes") Then
        If Not vNewVal = "<Nothing>" Then sNotes = vNewVal
        Item = sNotes
    ElseIf UCase(sStatName) = UCase("sPicture") Then
        If Not vNewVal = "<Nothing>" Then sPicture = vNewVal
        Item = sPicture
'    ElseIf ucase(sStatName) = ucase("sClass1" Then
'        If Not vNewVal = "<Nothing>" Then sClass1 = vNewVal
'        Item = sClass1
'    ElseIf ucase(sStatName) = ucase("sClass2" Then
'        If Not vNewVal = "<Nothing>" Then sClass2 = vNewVal
'        Item = sClass2
    ElseIf UCase(sStatName) = UCase("sSex") Then
        If Not vNewVal = "<Nothing>" Then sSex = vNewVal
        Item = sSex
    ElseIf UCase(sStatName) = UCase("sAlignment") Then
        If Not vNewVal = "<Nothing>" Then sAlignment = vNewVal
        Item = sAlignment
    
'    'Custom Class Info
'    ElseIf ucase(sStatName) = ucase("sCustomLevels" Then
'        If Not vNewVal = "<Nothing>" Then sCustomLevels = vNewVal
'        Item = sCustomLevels
'    ElseIf ucase(sStatName) = ucase("sCustomSaves" Then
'        If Not vNewVal = "<Nothing>" Then sCustomSaves = vNewVal
'        Item = sCustomSaves
'    ElseIf ucase(sStatName) = ucase("sCustomTHACO" Then
'        If Not vNewVal = "<Nothing>" Then sCustomTHACO = vNewVal
'        Item = sCustomTHACO
            
    'Abilities Tab
    'For Weapon 1
    ElseIf UCase(sStatName) = UCase("nCombatNumDice1") Then
        If Not vNewVal = "<Nothing>" Then nCombatNumDice1 = vNewVal
        Item = nCombatNumDice1
    ElseIf UCase(sStatName) = UCase("nCombatDice1") Then
        If Not vNewVal = "<Nothing>" Then nCombatDice1 = vNewVal
        Item = nCombatDice1
    ElseIf UCase(sStatName) = UCase("nCombatDamagePlus1") Then
        If Not vNewVal = "<Nothing>" Then nCombatDamagePlus1 = vNewVal
        Item = nCombatDamagePlus1
    ElseIf UCase(sStatName) = UCase("nCombatTHACOBonus1") Then
        If Not vNewVal = "<Nothing>" Then nCombatTHACOBonus1 = vNewVal
        Item = nCombatTHACOBonus1
    ElseIf UCase(sStatName) = UCase("nCombatNumOfAttacks1") Then
        If Not vNewVal = "<Nothing>" Then nCombatNumOfAttacks1 = vNewVal
        Item = nCombatNumOfAttacks1
    ElseIf UCase(sStatName) = UCase("nCombatNumOfAttacksEvery1") Then
        If Not vNewVal = "<Nothing>" Then nCombatNumOfAttacksEvery1 = vNewVal
        Item = nCombatNumOfAttacksEvery1
        
    'For Weapon 2
    ElseIf UCase(sStatName) = UCase("nCombatNumDice2") Then
        If Not vNewVal = "<Nothing>" Then nCombatNumDice2 = vNewVal
        Item = nCombatNumDice2
    ElseIf UCase(sStatName) = UCase("nCombatDice2") Then
        If Not vNewVal = "<Nothing>" Then nCombatDice2 = vNewVal
        Item = nCombatDice2
    ElseIf UCase(sStatName) = UCase("nCombatDamagePlus2") Then
        If Not vNewVal = "<Nothing>" Then nCombatDamagePlus2 = vNewVal
        Item = nCombatDamagePlus2
    ElseIf UCase(sStatName) = UCase("nCombatTHACOBonus2") Then
        If Not vNewVal = "<Nothing>" Then nCombatTHACOBonus2 = vNewVal
        Item = nCombatTHACOBonus2
    ElseIf UCase(sStatName) = UCase("nCombatNumOfAttacks2") Then
        If Not vNewVal = "<Nothing>" Then nCombatNumOfAttacks2 = vNewVal
        Item = nCombatNumOfAttacks2
    ElseIf UCase(sStatName) = UCase("nCombatNumOfAttacksEvery2") Then
        If Not vNewVal = "<Nothing>" Then nCombatNumOfAttacksEvery2 = vNewVal
        Item = nCombatNumOfAttacksEvery2
    
    ElseIf UCase(sStatName) = UCase("nCombatInitModifier") Then
        If Not vNewVal = "<Nothing>" Then nCombatInitModifier = vNewVal
        Item = nCombatInitModifier
    ElseIf UCase(sStatName) = UCase("nCombatACBonus") Then
        If Not vNewVal = "<Nothing>" Then nCombatACBonus = vNewVal
        Item = nCombatACBonus
        
    ElseIf UCase(sStatName) = UCase("nAC") Then
        If Not vNewVal = "<Nothing>" Then
            '04/04/2002 Chris Hill  Changed to use our error handling routines.  This way it will
            'conform to our standard.  When we turn off error checking this will turn off as well.
            Call Err.Raise(-1, sRoutineName, "AC is a read-only value")
            'Call g_oErrors.AddError("AC is a read-only value", -1, sRoutineName): GoTo ErrorHandler
        End If
        Item = AC()
        
    'No longer minion stat only
    ElseIf UCase(sStatName) = UCase("sAttackText1") Then
        If Not vNewVal = "<Nothing>" Then sAttackText1 = vNewVal
        Item = sAttackText1
    ElseIf UCase(sStatName) = UCase("sAttackText2") Then
        If Not vNewVal = "<Nothing>" Then sAttackText2 = vNewVal
        Item = sAttackText2
        
    'Minion Only Stats
    ElseIf UCase(sStatName) = UCase("bCombatAIOn") Then
        If Not vNewVal = "<Nothing>" Then bCombatAIOn = vNewVal
        Item = bCombatAIOn
    ElseIf UCase(sStatName) = UCase("bWillAttackParty") Then
        If Not vNewVal = "<Nothing>" Then bWillAttackParty = vNewVal
        Item = bWillAttackParty
    ElseIf UCase(sStatName) = UCase("bRangedCombat") Then
        If Not vNewVal = "<Nothing>" Then bRangedCombat = vNewVal
        Item = bRangedCombat
    ElseIf UCase(sStatName) = UCase("nTargetChoice") Then
        If Not vNewVal = "<Nothing>" Then nTargetChoice = vNewVal
        Item = nTargetChoice
    ElseIf UCase(sStatName) = UCase("nOwnerNumber") Then
        If Not vNewVal = "<Nothing>" Then nOwnerNumber = vNewVal
        Item = nOwnerNumber
        
    'New Class info
    ElseIf UCase(sStatName) = UCase("nClassID1") Then
        If Not vNewVal = "<Nothing>" Then nClassID1 = vNewVal
        Item = nClassID1
    ElseIf UCase(sStatName) = UCase("nClassID2") Then
        If Not vNewVal = "<Nothing>" Then nClassID2 = vNewVal
        Item = nClassID2
        
    ElseIf UCase(sStatName) = UCase("sProficiencies") Then
        If Not vNewVal = "<Nothing>" Then sProficiencies = vNewVal
        Item = sProficiencies
    ElseIf UCase(sStatName) = UCase("sLanguages") Then
        If Not vNewVal = "<Nothing>" Then sLanguages = vNewVal
        Item = sLanguages
        
    'ElseIf Len(sStatName) > 0 Then
        '04/04/2002 Chris Hill  Changed to use our error handling routines.  This way it will
        'conform to our standard.  When we turn off error checking this will turn off as well.
        'Call Err.Raise(-1, sRoutineName, "Stat [" & sStatName & "] Not Found")
        'Call g_oErrors.AddError("Stat [" & sStatName & "] Not Found", -1, sRoutineName): GoTo ErrorHandler
        
    ElseIf UCase(sStatName) = UCase("nWeaponID1") Then
        If Not vNewVal = "<Nothing>" Then nWeaponID1 = vNewVal
        Item = nWeaponID1
    ElseIf UCase(sStatName) = UCase("nWeaponID2") Then
        If Not vNewVal = "<Nothing>" Then nWeaponID2 = vNewVal
        Item = nWeaponID2
    ElseIf UCase(sStatName) = UCase("nCombatDamageBonus") Then
        If Not vNewVal = "<Nothing>" Then nCombatDamageBonus = vNewVal
        Item = nCombatDamageBonus
    
    ElseIf UCase(sStatName) = UCase("sWeapProf") Then
        If Not vNewVal = "<Nothing>" Then sWeapProf = vNewVal
        Item = sWeapProf
    
    ElseIf UCase(sStatName) = UCase("nMR") Then
        If Not vNewVal = "<Nothing>" Then nMR = vNewVal
        Item = nMR
        
    Else
        Item = m_cNotAStat
    End If


    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

'This is COMPLETELY experamental
Public Sub SetToForm(oForm As Form)
'
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & "SetToForm"
    Call WriteProcStart(sRoutineName)


    Dim i As Long
    Dim sStat As String
    For i = 0 To oForm.Controls.Count - 1
        'Get the stat value
        sStat = Item(oForm.Controls(i).Tag)
        'Is it really a stat?
        If Not sStat = m_cNotAStat Then
            If TypeOf oForm.Controls(i) Is Label Then
                oForm.Controls(i).Caption = sStat
            ElseIf TypeOf oForm.Controls(i) Is TextBox Or _
                   TypeOf oForm.Controls(i) Is ComboBox Or _
                   TypeOf oForm.Controls(i) Is RichTextBox Then
                oForm.Controls(i).Text = sStat
            ElseIf TypeOf oForm.Controls(i) Is CheckBox Then
                oForm.Controls(i).Value = BoolToInt(CBool(sStat))
            End If
        End If
    Next i


    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Sub
'This is COMPLETELY experamental
Public Sub GetFromForm(oForm As Form)

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & "GetFromForm"
    Call WriteProcStart(sRoutineName)


    Dim i As Long
    Dim sStat As String
    For i = 0 To oForm.Controls - 1
        'Get the stat value
        sStat = Item(oForm.Controls(i).Tag)
        'Is it really a stat?
        If Not sStat = m_cNotAStat Then
            If TypeOf oForm.Controls(i) Is Label Then
                Call Item(oForm.Controls(i).Tag, oForm.Controls(i).Caption)
            ElseIf TypeOf oForm.Controls(i) Is TextBox Then
                Call Item(oForm.Controls(i).Tag, oForm.Controls(i).Text)
            End If
        End If
    Next i


    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Sub

Public Function DeSerialize(sStats As String) As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".DeSerialize"
    Call WriteProcStart(sRoutineName)


    Dim sTemp As String
    Dim sRemStr As String
    sRemStr = sStats
    
    'GUID
    sGUID = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    
    'Vitals
    sName = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Name]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nMaxHP = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'MaxHP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nHP = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'HP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nSP = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'SP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nMaxSP = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'MaxSP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
                
    nExperience1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'MaxSP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nLevel1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'MaxSP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nExpForNextLevel1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'MaxSP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nExperience2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'MaxSP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nLevel2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'MaxSP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nExpForNextLevel2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'MaxSP]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'Stats
    nStr = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Strength]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sExceptionalStr = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'ExceptionalStr]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nDex = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Dexterity]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCon = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Constitution]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nInt = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Intelligence]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nWis = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Wisdom]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nChr = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Charisma]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCom = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Comliness]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nPer = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Perception]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'Character saves
    nDeath = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Para_Pois_Death]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nRod = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Rod_Staff_Wand]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nPoly = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Petrification_Polymorph]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nBreath = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'BreathWeapon]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nSpell = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'Visual
    nPhysicialAge = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nActualAge = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sRace = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nHeight = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nWeight = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sMarks = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sEyeColor = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sHairColor = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Spell]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'Combat
    nTHACOBonus = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'THAC0]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'Misc
    'sNotes = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'Notes]
    '    sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sPicture = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
        
'    sClass1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
'        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
'    sClass2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
'        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
        
    sSex = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sAlignment = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
'    'Custom Class Info
'    sCustomLevels = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'OnlineRefLevel]
'        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
'    sCustomSaves = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'OnlineRefSave]
'        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
'    sCustomTHACO = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'OnlineRefTHACO]
'        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'Abilities Tab
    'For Weapon 1
    nCombatNumDice1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatNumDice1]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatDice1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatDice1]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatDamagePlus1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatDamagePlus1]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatTHACOBonus1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatTHACOBonus1]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatNumOfAttacks1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatNumOfAttacks1]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatNumOfAttacksEvery1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatNumOfAttacksEvery1]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'For Weapon2
    nCombatNumDice2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatNumDice2]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatDice2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatDice2]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatDamagePlus2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatDamagePlus2]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatTHACOBonus2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatTHACOBonus2]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatNumOfAttacks2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatNumOfAttacks2]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nCombatNumOfAttacksEvery2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatNumOfAttacksEvery2]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
        
    nCombatACBonus = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatACBonus]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'nAC = 10 + GetDexACBonus(nDex) - nCombatACBonus

    nCombatInitModifier = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1) 'CombatInitModifier]
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    
    'No longer minion stat only
    sAttackText1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sAttackText2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
        
    'Minion Stats
    bCombatAIOn = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    bWillAttackParty = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nTargetChoice = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nOwnerNumber = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    
    'BattleMap Stats
    nX = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nY = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nDirection = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sCondition = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sState = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    bRangedCombat = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)

    m_nFontColor = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    sTemp = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
        Set m_oFont = DeSerializeFont(sTemp)

    nClassID1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nClassID2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)

    'To big!
    'sProficiencies = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
    '    sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    'sLanguages = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
    '    sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)

    nWeaponID1 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nWeaponID2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)

    nCombatDamageBonus = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    nMR = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
        sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
        
    'This has to go on the end so we can maintain version compatability.
    If Len(sRemStr) > 0 Then
        sAttackText2 = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
            sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    End If
    
    'This has to go on the end so we can maintain version compatability.
    If Len(sRemStr) > 0 Then
        bShowToUsers = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
            sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    End If
    If Len(sRemStr) > 0 Then
        bCanLootMinion = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
            sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    End If
    If Len(sRemStr) > 0 Then
        bGoesAwayWhenLooted = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
            sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
    End If
    
    
    If Len(sRemStr) > 0 Then
        sTemp = Left(sRemStr, InStr(sRemStr, m_cStatSep) - 1)
            sRemStr = Right(sRemStr, Len(sRemStr) - InStr(sRemStr, m_cStatSep) - Len(m_cStatSep) + 1)
        
        sTemp = Replace(sTemp, m_cTempItemSep, m_cStatSep)
        Call m_oItems.DeSerialize(sTemp)
    End If
    
           
    DeSerialize = sRemStr
    
    
    Call WriteProcStop(sRoutineName) ' sRemStr)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function Copy() As clsCharacter

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".Copy"
    Call WriteProcStart(sRoutineName)
    
    
    Dim sChar As String
    sChar = Serialize()
    Set Copy = New clsCharacter
    Call Copy.DeSerialize(sChar)
    
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function Serialize(Optional bItemsToo As Boolean = False) As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".Serialize"
    Call WriteProcStart(sRoutineName)
    
    
    Dim sTemp As String
    
    'GUID
    Serialize = sGUID & m_cStatSep
    'Vitals
    Serialize = Serialize & sName & m_cStatSep 'Name]
    Serialize = Serialize & nMaxHP & m_cStatSep 'MaxHP]
    Serialize = Serialize & nHP & m_cStatSep 'HP]
    Serialize = Serialize & nSP & m_cStatSep 'SP]
    Serialize = Serialize & nMaxSP & m_cStatSep 'MaxSP]
    
    Serialize = Serialize & nExperience1 & m_cStatSep 'MaxSP]
    Serialize = Serialize & nLevel1 & m_cStatSep 'MaxSP]
    Serialize = Serialize & nExpForNextLevel1 & m_cStatSep 'MaxSP]
    Serialize = Serialize & nExperience2 & m_cStatSep 'MaxSP]
    Serialize = Serialize & nLevel2 & m_cStatSep 'MaxSP]
    Serialize = Serialize & nExpForNextLevel2 & m_cStatSep 'MaxSP]
    'Stats
    Serialize = Serialize & nStr & m_cStatSep 'Strength]
    Serialize = Serialize & sExceptionalStr & m_cStatSep 'ExceptionalStr]
    Serialize = Serialize & nDex & m_cStatSep 'Dexterity]
    Serialize = Serialize & nCon & m_cStatSep 'Constitution]
    Serialize = Serialize & nInt & m_cStatSep 'Intelligence]
    Serialize = Serialize & nWis & m_cStatSep 'Wisdom]
    Serialize = Serialize & nChr & m_cStatSep 'Charisma]
    Serialize = Serialize & nCom & m_cStatSep 'Comliness]
    Serialize = Serialize & nPer & m_cStatSep 'Perception]
    'Character saves
    Serialize = Serialize & nDeath & m_cStatSep 'Para_Pois_Death]
    Serialize = Serialize & nRod & m_cStatSep 'Rod_Staff_Wand]
    Serialize = Serialize & nPoly & m_cStatSep 'Petrification_Polymorph]
    Serialize = Serialize & nBreath & m_cStatSep 'BreathWeapon]
    Serialize = Serialize & nSpell & m_cStatSep 'Spell]
    'Visual
    Serialize = Serialize & nPhysicialAge & m_cStatSep
    Serialize = Serialize & nActualAge & m_cStatSep
    Serialize = Serialize & sRace & m_cStatSep
    Serialize = Serialize & nHeight & m_cStatSep
    Serialize = Serialize & nWeight & m_cStatSep
    Serialize = Serialize & sMarks & m_cStatSep
    Serialize = Serialize & sEyeColor & m_cStatSep
    Serialize = Serialize & sHairColor & m_cStatSep
    'Combat
    Serialize = Serialize & nTHACOBonus & m_cStatSep 'THAC0]
    'Misc
    'Serialize = Serialize & sNotes & m_cStatSep
    Serialize = Serialize & sPicture & m_cStatSep
    Serialize = Serialize & sSex & m_cStatSep
    Serialize = Serialize & sAlignment & m_cStatSep
    'Abilities Tab
    'For Weapon 1
    Serialize = Serialize & nCombatNumDice1 & m_cStatSep 'CombatNumDice1]
    Serialize = Serialize & nCombatDice1 & m_cStatSep 'CombatDice1]
    Serialize = Serialize & nCombatDamagePlus1 & m_cStatSep 'CombatDamagePlus1]
    Serialize = Serialize & nCombatTHACOBonus1 & m_cStatSep 'CombatTHACOBonus1]
    Serialize = Serialize & nCombatNumOfAttacks1 & m_cStatSep 'CombatNumOfAttacks1]
    Serialize = Serialize & nCombatNumOfAttacksEvery1 & m_cStatSep 'CombatNumOfAttacksEvery1]
    'For Weapon 2
    Serialize = Serialize & nCombatNumDice2 & m_cStatSep 'CombatNumDice2]
    Serialize = Serialize & nCombatDice2 & m_cStatSep 'CombatDice2]
    Serialize = Serialize & nCombatDamagePlus2 & m_cStatSep 'CombatDamagePlus2]
    Serialize = Serialize & nCombatTHACOBonus2 & m_cStatSep 'CombatTHACOBonus2]
    Serialize = Serialize & nCombatNumOfAttacks2 & m_cStatSep 'CombatNumOfAttacks2]
    Serialize = Serialize & nCombatNumOfAttacksEvery2 & m_cStatSep 'CombatNumOfAttacksEvery2]
    
    Serialize = Serialize & nCombatACBonus & m_cStatSep 'CombatACBonus]
    'nAC = 10 + GetDexACBonus(nDex) - nCombatACBonus
    
    Serialize = Serialize & nCombatInitModifier & m_cStatSep 'CombatInitModifier]
        
    'No longer minion stat only
    Serialize = Serialize & sAttackText1 & m_cStatSep
    Serialize = Serialize & sAttackText2 & m_cStatSep
    
    'Minion Stats
    Serialize = Serialize & bCombatAIOn & m_cStatSep
    Serialize = Serialize & bWillAttackParty & m_cStatSep
    Serialize = Serialize & nTargetChoice & m_cStatSep
    Serialize = Serialize & nOwnerNumber & m_cStatSep
    'BattleMap Stats
    Serialize = Serialize & nX & m_cStatSep
    Serialize = Serialize & nY & m_cStatSep
    Serialize = Serialize & nDirection & m_cStatSep
    Serialize = Serialize & sCondition & m_cStatSep
    Serialize = Serialize & sState & m_cStatSep
    Serialize = Serialize & bRangedCombat & m_cStatSep
    
    'Now serailize their font color and choice
    Serialize = Serialize & m_nFontColor & m_cStatSep
    Serialize = Serialize & SerializeFont(m_oFont) & m_cStatSep
    
    Serialize = Serialize & nClassID1 & m_cStatSep
    Serialize = Serialize & nClassID2 & m_cStatSep
    
    'To big!
    'Serialize = Serialize & sProficiencies & m_cStatSep
    'Serialize = Serialize & sLanguages & m_cStatSep
    
    Serialize = Serialize & nWeaponID1 & m_cStatSep
    Serialize = Serialize & nWeaponID2 & m_cStatSep
    
    Serialize = Serialize & nCombatDamageBonus & m_cStatSep
    Serialize = Serialize & nMR & m_cStatSep
    
    Serialize = Serialize & sAttackText2 & m_cStatSep
    Serialize = Serialize & bShowToUsers & m_cStatSep
    Serialize = Serialize & bCanLootMinion & m_cStatSep
    Serialize = Serialize & bGoesAwayWhenLooted & m_cStatSep
    
    '07/20/2004 Chris Hill  We have to hack this because the item seperator is the same as the char sep.
    If bItemsToo = True Then
        sTemp = m_oItems.Serialize()
        sTemp = Replace(sTemp, m_cStatSep, m_cTempItemSep)
        Serialize = Serialize & sTemp & m_cStatSep
    End If
    
    
    Call WriteProcStop(sRoutineName) ' Serialize)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

'04/04/2002 Chris Hill  Trimmed all incoming strings.  This is becuase in order to work
'around the no-blank-strings DB setting I am adding spaces.  The user won't want to see those though.
Public Sub Load(dbDND As ADODB.Connection, nCharacterID As Long)

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".Load"
    Call WriteProcStart(sRoutineName)
    
    
    Dim rstChar As ADODB.Recordset
    Dim bFoundIt As Boolean
    
    Call m_oTableHelperCharacter.LoadSchema(dbDND, "CharacterInfo")
    Call m_oTableHelperCharacter.LoadRecords("WHERE CharacterID = " & nCharacterID)
    
    nCharID = nCharacterID
        
    'Vitals
    sName = Trim(m_oTableHelperCharacter.GetValue("Name"))
    nMaxHP = m_oTableHelperCharacter.GetValue("MaxHP") ', 0)
    nHP = m_oTableHelperCharacter.GetValue("HP") ', 0)
    nSP = m_oTableHelperCharacter.GetValue("SP") ', 0)
    nMaxSP = m_oTableHelperCharacter.GetValue("MaxSP") ', 0)
    nExperience1 = m_oTableHelperCharacter.GetValue("Experience1") ', 0)
    nLevel1 = m_oTableHelperCharacter.GetValue("Level1") ', 0)
    nExpForNextLevel1 = m_oTableHelperCharacter.GetValue("ExpForNextLevel1") ', 0)
    nExperience2 = m_oTableHelperCharacter.GetValue("Experience2") ', 0)
    nLevel2 = m_oTableHelperCharacter.GetValue("Level2") ', 0)
    nExpForNextLevel2 = m_oTableHelperCharacter.GetValue("ExpForNextLevel2") ', 0)
    'Stats
    nStr = m_oTableHelperCharacter.GetValue("Strength") ', 0)
    sExceptionalStr = Trim(m_oTableHelperCharacter.GetValue("ExceptionalStr"))
        If Not nStr = 18 Then
            sExceptionalStr = ""
        End If
    nDex = m_oTableHelperCharacter.GetValue("Dexterity") ', 0)
    nCon = m_oTableHelperCharacter.GetValue("Constitution") ', 0)
    nInt = m_oTableHelperCharacter.GetValue("Intelligence") ', 0)
    nWis = m_oTableHelperCharacter.GetValue("Wisdom") ', 0)
    nChr = m_oTableHelperCharacter.GetValue("Charisma") ', 0)
    nCom = m_oTableHelperCharacter.GetValue("Comliness") ', 0)
    nPer = m_oTableHelperCharacter.GetValue("Perception") ', 0)
    'Character saves
    nDeath = m_oTableHelperCharacter.GetValue("Para_Pois_Death") ', 0)
    nRod = m_oTableHelperCharacter.GetValue("Rod_Staff_Wand") ', 0)
    nPoly = m_oTableHelperCharacter.GetValue("Petrification_Polymorph") ', 0)
    nBreath = m_oTableHelperCharacter.GetValue("BreathWeapon") ', 0)
    nSpell = m_oTableHelperCharacter.GetValue("Spell") ', 0)
    'Visual
    nPhysicialAge = m_oTableHelperCharacter.GetValue("PhysicalAge") ', 0)
    nActualAge = m_oTableHelperCharacter.GetValue("ActualAge") ', 0)
    sRace = Trim(m_oTableHelperCharacter.GetValue("Race"))
    nHeight = m_oTableHelperCharacter.GetValue("Height") ', 0)
    nWeight = m_oTableHelperCharacter.GetValue("Weight") ', 0)
    sMarks = Trim(m_oTableHelperCharacter.GetValue("DistinguishingMarks"))
    sEyeColor = Trim(m_oTableHelperCharacter.GetValue("EyeColor"))
    sHairColor = Trim(m_oTableHelperCharacter.GetValue("HairColor"))
    'Combat
    nTHACOBonus = m_oTableHelperCharacter.GetValue("THAC0") ', 0)
    'Misc
    sNotes = Trim(m_oTableHelperCharacter.GetValue("Notes"))
    sPicture = Trim(m_oTableHelperCharacter.GetValue("CharPicture"))
    sSex = Trim(m_oTableHelperCharacter.GetValue("Sex"))
    sAlignment = Trim(m_oTableHelperCharacter.GetValue("Alignment"))
    'Abilities Tab
    'For Weapon 1
    nCombatNumDice1 = m_oTableHelperCharacter.GetValue("CombatNumDice1") ', 0)
    nCombatDice1 = m_oTableHelperCharacter.GetValue("CombatDice1") ', 0)
    nCombatDamagePlus1 = m_oTableHelperCharacter.GetValue("CombatDamagePlus1") ', 0)
    nCombatTHACOBonus1 = m_oTableHelperCharacter.GetValue("CombatTHACOBonus1") ', 0)
    nCombatNumOfAttacks1 = m_oTableHelperCharacter.GetValue("CombatNumOfAttacks1") ', 0)
    nCombatNumOfAttacksEvery1 = m_oTableHelperCharacter.GetValue("CombatNumOfAttacksEvery1") ', 0)
    '04/16/2002 Chris Hill  0 and below make no sence... so set it to 1.
    If nCombatDice1 <= 0 Then nCombatDice1 = 1
    If nCombatNumOfAttacksEvery1 <= 0 Then nCombatNumOfAttacksEvery1 = 1
    If nCombatNumOfAttacks1 <= 0 Then nCombatNumOfAttacks1 = 1
    If nCombatNumDice1 <= 0 Then nCombatNumDice1 = 1
    
    'For Weapon 2
    nCombatNumDice2 = m_oTableHelperCharacter.GetValue("CombatNumDice2") ', 0)
    nCombatDice2 = m_oTableHelperCharacter.GetValue("CombatDice2") ', 0)
    nCombatDamagePlus2 = m_oTableHelperCharacter.GetValue("CombatDamagePlus2") ', 0)
    nCombatTHACOBonus2 = m_oTableHelperCharacter.GetValue("CombatTHACOBonus2") ', 0)
    nCombatNumOfAttacks2 = m_oTableHelperCharacter.GetValue("CombatNumOfAttacks2") ', 0)
    nCombatNumOfAttacksEvery2 = m_oTableHelperCharacter.GetValue("CombatNumOfAttacksEvery2") ', 0)
    
    nCombatACBonus = m_oTableHelperCharacter.GetValue("CombatACBonus") ', 0)
    nCombatInitModifier = m_oTableHelperCharacter.GetValue("CombatInitModifier") ', 0)
        
    'Load our font info
    Set m_oFont = DeSerializeFont(Trim(m_oTableHelperCharacter.GetValue("Font")))
    m_nFontColor = m_oTableHelperCharacter.GetValue("FontColor") ', 0)
    
    'Set some constants that the minions normally take care of for us
    bWillAttackParty = False

    nClassID1 = m_oTableHelperCharacter.GetValue("nClass1ID")
    nClassID2 = m_oTableHelperCharacter.GetValue("nClass2ID")
        
    sProficiencies = m_oTableHelperCharacter.GetValue("sProficiencies")
    sLanguages = m_oTableHelperCharacter.GetValue("sLanguages")

    'The ID of the item you may or may not be using
    nWeaponID1 = m_oTableHelperCharacter.GetValue("nWeaponID1")
    nWeaponID2 = m_oTableHelperCharacter.GetValue("nWeaponID2")
        
    nCombatDamageBonus = m_oTableHelperCharacter.GetValue("nCombatDamageBonus")
    bLoadedLastTime = m_oTableHelperCharacter.GetValue("bLoadedLastTime")
    
    sWeapProf = m_oTableHelperCharacter.GetValue("sWeapProf")
    nMR = m_oTableHelperCharacter.GetValue("nMR")
                        
    'Save our attack text descriptions
    sAttackText1 = m_oTableHelperCharacter.GetValue("sAttackText1")
    sAttackText2 = m_oTableHelperCharacter.GetValue("sAttackText2")

            
    Call m_oSpells.Load(dbDND, nCharID)
    Call m_oItems.Load(dbDND, nCharID)
    Call m_oMacros.Load(dbDND, nCharID)
        
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Sub
'04/04/2002 Chris Hill  Added a space to all fields.  This is because when the DB was
'originally designed it was setup to not allow blank string fields.  Curses!
Public Sub Save(dbDND As ADODB.Connection)

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".Save"
    Call WriteProcStart(sRoutineName)
    
    
    Dim rstChar As ADODB.Recordset
    
    'This is a stupid thing to do I know... you see minions have a negative charID.  Another stupid thing to do, yes.  But somewhere
    'a minion is getting saved into the DB and I don't want that.  So this is my last resort, my "I'm too tired to bother debuging
    'to the true problem" fix.
    If nCharID < 0 Then
        Exit Sub
    End If
    
    'When making a new character we have to make sure to force the schema toload
    If m_oTableHelperCharacter.IsRecordLoaded() = False Then
        Call m_oTableHelperCharacter.LoadSchema(dbDND, "CharacterInfo")
    Else
        If nExperience1 = 0 And m_oTableHelperCharacter.GetValue("Experience1") > 0 Then
            Call Err.Raise(-1, "DNDOnline.clsCharacter.Save", "Warning!  Character that once had experiance now has zero.  This is likely a bug, save aborted.")
        End If
    End If
       
    'Vitals
    m_oTableHelperCharacter.SetValue("Name") = sName & " "
    m_oTableHelperCharacter.SetValue("MaxHP") = nMaxHP
    m_oTableHelperCharacter.SetValue("HP") = nHP
    m_oTableHelperCharacter.SetValue("SP") = nSP
    m_oTableHelperCharacter.SetValue("MaxSP") = nMaxSP
    m_oTableHelperCharacter.SetValue("Experience1") = nExperience1
    m_oTableHelperCharacter.SetValue("Experience2") = nExperience2
    m_oTableHelperCharacter.SetValue("Level1") = nLevel1
    m_oTableHelperCharacter.SetValue("Level2") = nLevel2
    m_oTableHelperCharacter.SetValue("ExpForNextLevel1") = nExpForNextLevel1
    m_oTableHelperCharacter.SetValue("ExpForNextLevel2") = nExpForNextLevel2
    'Stats
    m_oTableHelperCharacter.SetValue("Strength") = nStr
    m_oTableHelperCharacter.SetValue("ExceptionalStr") = sExceptionalStr & " "
    m_oTableHelperCharacter.SetValue("Dexterity") = nDex
    m_oTableHelperCharacter.SetValue("Constitution") = nCon
    m_oTableHelperCharacter.SetValue("Intelligence") = nInt
    m_oTableHelperCharacter.SetValue("Wisdom") = nWis
    m_oTableHelperCharacter.SetValue("Charisma") = nChr
    m_oTableHelperCharacter.SetValue("Comliness") = nCom
    m_oTableHelperCharacter.SetValue("Perception") = nPer
    'Character saves
    m_oTableHelperCharacter.SetValue("Para_Pois_Death") = nDeath
    m_oTableHelperCharacter.SetValue("Rod_Staff_Wand") = nRod
    m_oTableHelperCharacter.SetValue("Petrification_Polymorph") = nPoly
    m_oTableHelperCharacter.SetValue("BreathWeapon") = nBreath
    m_oTableHelperCharacter.SetValue("Spell") = nSpell
    'Visual
    m_oTableHelperCharacter.SetValue("PhysicalAge") = nPhysicialAge
    m_oTableHelperCharacter.SetValue("ActualAge") = nActualAge
    m_oTableHelperCharacter.SetValue("Race") = sRace & " "
    m_oTableHelperCharacter.SetValue("Height") = nHeight
    m_oTableHelperCharacter.SetValue("Weight") = nWeight
    m_oTableHelperCharacter.SetValue("DistinguishingMarks") = sMarks & " "
    m_oTableHelperCharacter.SetValue("EyeColor") = sEyeColor & " "
    m_oTableHelperCharacter.SetValue("HairColor") = sHairColor & " "
    'Combat
    m_oTableHelperCharacter.SetValue("THAC0") = nTHACOBonus
    m_oTableHelperCharacter.SetValue("AC") = 0
    'Misc
    'm_oTableHelperCharacter.SetValue("Notes") = sNotes & " "
    m_oTableHelperCharacter.SetValue("CharPicture") = sPicture & " "
'    m_otablehelpercharacter.SetValue("Class1") = sClass1 & " "
'    m_otablehelpercharacter.SetValue("Class2") = sClass2 & " "
    m_oTableHelperCharacter.SetValue("Sex") = sSex & " "
    m_oTableHelperCharacter.SetValue("Alignment") = sAlignment & " "
    
'    'Did we just add this record?
'    If IsNull(m_oTableHelperCharacter.GetValue("CharacterID")) = True Then
'        Set rstChar = OpenMyRecordset(dbDND, "SELECT * FROM CharacterInfo")
'        Call rstChar.MoveLast
'        nCharID = m_oTableHelperCharacter.GetValue("CharacterID")
'    End If

    'Abilities Tab
    'For Weapon 1
    m_oTableHelperCharacter.SetValue("CombatNumDice1") = nCombatNumDice1
    m_oTableHelperCharacter.SetValue("CombatDice1") = nCombatDice1
    m_oTableHelperCharacter.SetValue("CombatDamagePlus1") = nCombatDamagePlus1
    m_oTableHelperCharacter.SetValue("CombatTHACOBonus1") = nCombatTHACOBonus1
    m_oTableHelperCharacter.SetValue("CombatNumOfAttacks1") = nCombatNumOfAttacks1
    m_oTableHelperCharacter.SetValue("CombatNumOfAttacksEvery1") = nCombatNumOfAttacksEvery1
    'For Weapon 2
    m_oTableHelperCharacter.SetValue("CombatNumDice2") = nCombatNumDice2
    m_oTableHelperCharacter.SetValue("CombatDice2") = nCombatDice2
    m_oTableHelperCharacter.SetValue("CombatDamagePlus2") = nCombatDamagePlus2
    m_oTableHelperCharacter.SetValue("CombatTHACOBonus2") = nCombatTHACOBonus2
    m_oTableHelperCharacter.SetValue("CombatNumOfAttacks2") = nCombatNumOfAttacks2
    m_oTableHelperCharacter.SetValue("CombatNumOfAttacksEvery2") = nCombatNumOfAttacksEvery2
    
    m_oTableHelperCharacter.SetValue("CombatACBonus") = nCombatACBonus
    m_oTableHelperCharacter.SetValue("CombatInitModifier") = nCombatInitModifier
        
    'Load our font info
    m_oTableHelperCharacter.SetValue("Font") = SerializeFont(m_oFont)
    m_oTableHelperCharacter.SetValue("FontColor") = m_nFontColor
        
    m_oTableHelperCharacter.SetValue("nClass1ID") = nClassID1
    If nClassID2 > 0 Then
        m_oTableHelperCharacter.SetValue("nClass2ID") = nClassID2
    Else
        m_oTableHelperCharacter.SetValue("nClass2ID") = Null
    End If
    
    m_oTableHelperCharacter.SetValue("sProficiencies") = sProficiencies
    m_oTableHelperCharacter.SetValue("sLanguages") = sLanguages
    
    m_oTableHelperCharacter.SetValue("Notes") = sNotes & " "
    'Weapon ID's
    m_oTableHelperCharacter.SetValue("nWeaponID1") = nWeaponID1
    m_oTableHelperCharacter.SetValue("nWeaponID2") = nWeaponID2
    
    m_oTableHelperCharacter.SetValue("nCombatDamageBonus") = nCombatDamageBonus
    m_oTableHelperCharacter.SetValue("bLoadedLastTime") = bLoadedLastTime
    
    m_oTableHelperCharacter.SetValue("sWeapProf") = sWeapProf
    m_oTableHelperCharacter.SetValue("nMR") = nMR
    
    'Save our attack text descriptions
    m_oTableHelperCharacter.SetValue("sAttackText1") = sAttackText1
    m_oTableHelperCharacter.SetValue("sAttackText2") = sAttackText2
    
    
    nCharID = m_oTableHelperCharacter.Save
    
    Call m_oSpells.Save(dbDND)
    Call m_oItems.Save(dbDND)
    Call m_oMacros.Save(dbDND)
        
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Sub
Public Function GetInit(Optional ByRef nRolled As Long, _
                        Optional ByRef nStatBonus As Long, Optional ByRef nCharModifier As Long, Optional ByRef nItemBonus As Long, _
                        Optional ByRef sStatBonusSrc As String, Optional ByRef sCharModifierSrc As String, Optional ByRef sItemBonusSrc As String) As Long
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetInit"
    Call WriteProcStart(sRoutineName)
    
    
    Dim oItem1 As clsItem, oItem2 As clsItem
    Dim nItemBonus1 As Long, nItemBonus2 As Long
    Dim nCurLevel As Long
    
    nStatBonus = 0
        sStatBonusSrc = "<No Stat Init Bonuses>"
    nCharModifier = nCombatInitModifier
        If nCharModifier = 0 Then
            sCharModifierSrc = ""
        Else
            sCharModifierSrc = "Char Mod = " & nCharModifier
        End If

    '12/11/2002 Chris Hill  Add in any proficiancy bonuses you get
    If IsItemWeapon1User() = True Then
        Set oItem1 = m_oItems.GetItemByID(nWeaponID1)
        If oItem1 Is Nothing Then
            Call Err.Raise(-1, sRoutineName, "Unable to find item currently assigned as primary weapon.")
        Else
            nCurLevel = GetWeapProfLevel(oItem1.m_sWeapProfType)
            Call GetWeapSpecializedBonuses(nCurLevel, , , nItemBonus1)
        End If
    End If
    If IsItemWeapon2User() = True Then
        Set oItem2 = m_oItems.GetItemByID(nWeaponID2)
        If oItem2 Is Nothing Then
            Call Err.Raise(-1, sRoutineName, "Unable to find item currently assigned as secondary weapon.")
        Else
            nCurLevel = GetWeapProfLevel(oItem2.m_sWeapProfType)
            Call GetWeapSpecializedBonuses(nCurLevel, , , nItemBonus2)
        End If
    End If
    
    If Len(sCharModifierSrc) > 0 Then
        sCharModifierSrc = sCharModifierSrc & ", "
    End If
    If Not nItemBonus1 = 0 Then
        sCharModifierSrc = sCharModifierSrc & "Weap 1 Prof. = " & nItemBonus1
    End If
    If Not nItemBonus2 = 0 Then
        sCharModifierSrc = sCharModifierSrc & "Weap 2 Prof. = " & nItemBonus2
    End If
    nCharModifier = nCharModifier + nItemBonus1 + nItemBonus2
    
    sItemBonusSrc = ""
    nItemBonus = GetItemInitBonus(sItemBonusSrc)
    nRolled = Int((10 * Rnd) + 1)
    'Chris - is good for items
    GetInit = nRolled - nStatBonus + nCharModifier + nItemBonus
    'GetInit = nRolled - nStatBonus + nCharModifier - nItemBonus
        
    If Len(sItemBonusSrc) = 0 Then
        sItemBonusSrc = "<No Item Init Bonuses>"
    End If
        
    
    Call WriteProcStop(sRoutineName) ' AC)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function

Public Function AC(Optional ByRef nDexBonus As Long, Optional ByRef nCharModifier As Long, Optional ByRef nItemBonus As Long, _
                   Optional ByRef sDexBonusSrc As String, Optional ByRef sCharModifierSrc As String, Optional ByRef sItemBonusSrc As String) As Long
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".AC"
    Call WriteProcStart(sRoutineName)
    
    
    nDexBonus = GetDexACBonus()
        sDexBonusSrc = "Dex of " & Item("nDex") & " = " & nDexBonus
        
    nCharModifier = nCombatACBonus
        If nCharModifier > 0 Then
            sCharModifierSrc = "Char Mod = " & nCharModifier
        End If
        '12/17/2002 Chris Hill  Factor the single weapon prof into the mix
        If GetWeapProfLevel(g_cSINGLEWEAPPROF) > 0 And IsDualWeaponUser() = False And IsItemWeapon2User() = False Then
            If Len(sCharModifierSrc) > 0 Then sCharModifierSrc = sCharModifierSrc & ", "
            nCharModifier = nCharModifier + 2
            sCharModifierSrc = sCharModifierSrc & "Single Weap Prof = 2"
        End If
        If Len(sCharModifierSrc) = 0 Then
            sCharModifierSrc = "<No Item AC Bonuses>"
        End If
        
    nItemBonus = GetItemACBonus(sItemBonusSrc)
        If Len(sItemBonusSrc) = 0 Then
            sItemBonusSrc = "<No Item AC Bonuses>"
        End If
    AC = 10 + nDexBonus - nCharModifier - nItemBonus
        
    
    Call WriteProcStop(sRoutineName) ' AC)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function

Public Function GetDexTHACOBonus() As Long
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetDexTHACOBonus"
    Call WriteProcStart(sRoutineName)
    
    
    Dim nRow As Long, nCol As Long
    Dim oTableDexterity As clsTable
    
    oTableDexterity = g_oTables.GetTableByNode("Dexterity")
    nRow = oTableDexterity.FindRow(nDex)
    nCol = oTableDexterity.FindColumn("Reaction Adj.")
    
    If Not nRow = -1 And Not nCol = -1 Then
        GetDexTHACOBonus = oTableDexterity(nCol, nRow)
    Else
        Call Err.Raise(-1, "DNDOnline.clsCharacter.GetDexTHACOBonus", "Unable to locate your dex <" & nDex & "> on the deterity table")
    End If
        
        
    Call WriteProcStop(sRoutineName) ' GetDexTHACOBonus)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function

Public Function GetDexACBonus() As Long
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetDexACBonus"
    Call WriteProcStart(sRoutineName)
    
    
    Dim nRow As Long, nCol As Long
    Dim oTableDexterity As clsTable
    
    Set oTableDexterity = g_oTables.GetTableByNode("Dexterity")
    nRow = oTableDexterity.FindRow(nDex)
    nCol = oTableDexterity.FindColumn("Defensive Adj.")
    
    If Not nRow = -1 And Not nCol = -1 Then
        GetDexACBonus = oTableDexterity.Data(nCol, nRow)
    Else
        Call Err.Raise(-1, "DNDOnline.clsCharacter.GetDexACBonus", "Unable to locate your dex <" & nDex & "> on the deterity table")
    End If
    
        
    Call WriteProcStop(sRoutineName) ' GetDexACBonus)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Public Function GetStrTHACOBonus() As Long
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetStrTHACOBonus"
    Call WriteProcStart(sRoutineName)
    
    
    Dim nRow As Long, nCol As Long
    Dim oTableStrength As clsTable
    Dim sTemp As String
    
    sTemp = Trim(CStr(nStr))
    If Len(sExceptionalStr) > 0 Then
        sTemp = sTemp & "/" & sExceptionalStr
    End If
    Set oTableStrength = g_oTables.GetTableByNode("Strength")
    nRow = oTableStrength.FindRow(sTemp)
    nCol = oTableStrength.FindColumn("Hit Prob.")
    
    If Not nRow = -1 And Not nCol = -1 Then
        sTemp = oTableStrength.Data(nCol, nRow)
        If sTemp = "Normal" Then
            GetStrTHACOBonus = 0
        Else
            GetStrTHACOBonus = sTemp
        End If
    Else
        Call Err.Raise(-1, "DNDOnline.clsCharacter.GetStrTHACOBonus", "Unable to locate your CStr <" & Trim(CStr(nStr)) & "/" & sExceptionalStr & "> on the strength table")
    End If
        
        
    Call WriteProcStop(sRoutineName) ' GetStrTHACOBonus)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Public Function GetStrDamBonus() As Long
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetStrDamBonus"
    Call WriteProcStart(sRoutineName)
    
    
    Dim nRow As Long, nCol As Long
    Dim oTableStrength As clsTable
    Dim sTemp As String
    
    sTemp = Trim(CStr(nStr))
    If Len(sExceptionalStr) > 0 Then
        sTemp = sTemp & "/" & sExceptionalStr
    End If
    Set oTableStrength = g_oTables.GetTableByNode("Strength")
    nRow = oTableStrength.FindRow(sTemp)
    nCol = oTableStrength.FindColumn("Damage Adj.")
    
    If Not nRow = -1 And Not nCol = -1 Then
        sTemp = oTableStrength.Data(nCol, nRow)
        If sTemp = "None" Then
            GetStrDamBonus = 0
        Else
            GetStrDamBonus = oTableStrength.Data(nCol, nRow)
        End If
    Else
        Call Err.Raise(-1, "DNDOnline.clsCharacter.GetStrDamBonus", "Unable to locate your CStr <" & Trim(CStr(nStr)) & "/" & sExceptionalStr & "> on the strength table")
    End If
        
        
    Call WriteProcStop(sRoutineName) ' GetStrDamBonus)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Public Function GetItemWeapon(nWeapon As Long) As clsItem

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetItemWeapon"
    Call WriteProcStart(sRoutineName)
    
    
    If nWeapon = 1 Then
        If IsItemWeapon1User() = True Then
            Set GetItemWeapon = m_oItems.GetItemByID(nWeaponID1)
            If GetItemWeapon Is Nothing Then
                Call Err.Raise(-1, sRoutineName, "Unable to find item currently assigned as primary weapon.")
            End If
        Else
            Set GetItemWeapon = Nothing
        End If
    Else
        If IsItemWeapon2User() = True Then
            Set GetItemWeapon = m_oItems.GetItemByID(nWeaponID2)
            If GetItemWeapon Is Nothing Then
                Call Err.Raise(-1, sRoutineName, "Unable to find item currently assigned as secondary weapon.")
            End If
        Else
            Set GetItemWeapon = Nothing
        End If
    End If

        
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
            
End Function
Public Sub GetDamage(nWeapon As Long, _
                        Optional ByRef nTotalMin As Long, Optional nTotalMax As Long, _
                        Optional ByRef nWeaponMin As Long, Optional ByRef nWeaponMax As Long, Optional ByRef sWeaponDesc As String, _
                        Optional ByRef nStrBonus As Long, Optional ByRef sStrDesc As String, _
                        Optional ByRef nProf As Long, Optional ByRef sProfDesc As String, _
                        Optional ByRef nCharMod As Long, Optional ByRef sCharModDesc As String)
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetDamage"
    Call WriteProcStart(sRoutineName)
    
    
    Dim oItem As clsItem
    Dim nCurLevel As Long
    Dim nTemp As Long
    
    sWeaponDesc = ""
    sStrDesc = ""
    sProfDesc = ""
    sCharModDesc = ""
    
    'Get the char modifier
    nCharMod = nCombatDamageBonus
    If nCharMod <> 0 Then
        sCharModDesc = "Char Mod = " & nCharMod
    End If
    
    'Get the str bonus
    nStrBonus = GetStrDamBonus()
    sStrDesc = nStr
    If Len(sExceptionalStr) > 0 Then
        sStrDesc = sStrDesc & "/" & sExceptionalStr
    End If
    If nStrBonus < 0 Then
        sStrDesc = sStrDesc & " = " & nStrBonus
    ElseIf nStrBonus > 0 Then
        sStrDesc = sStrDesc & " = +" & nStrBonus
    Else
        sStrDesc = ""
    End If
    
    'Weapon Stats
    Set oItem = GetItemWeapon(nWeapon)
    If oItem Is Nothing Then
        If nWeapon = 1 Then
            nWeaponMin = nCombatDamagePlus1 + nCombatNumDice1
            nWeaponMax = (nCombatDamagePlus1 + nCombatNumDice1 * nCombatDice1)
            sWeaponDesc = "Weap " & nWeapon & " = " & nCombatNumDice1 & "D" & nCombatDice1
            If nCombatDamagePlus1 > 0 Then
                sWeaponDesc = sWeaponDesc & "+" & nCombatDamagePlus1
            ElseIf nCombatDamagePlus1 < 0 Then
                sWeaponDesc = sWeaponDesc & nCombatDamagePlus1
            End If
        Else
            nWeaponMin = nCombatDamagePlus2 + nCombatNumDice2
            nWeaponMax = nWeaponMin & (nCombatDamagePlus2 + nCombatNumDice2 * nCombatDice2)
            sWeaponDesc = "Weap " & nWeapon & " = " & nCombatNumDice2 & "D" & nCombatDice2
            If nCombatDamagePlus2 > 0 Then
                sWeaponDesc = sWeaponDesc & "+" & nCombatDamagePlus2
            ElseIf nCombatDamagePlus2 < 0 Then
                sWeaponDesc = sWeaponDesc & nCombatDamagePlus2
            End If
        End If
    Else
        '12/11/2002 Chris Hill  Proficiancy level for this weapon!
        nCurLevel = GetWeapProfLevel(oItem.m_sWeapProfType)
        Call GetWeapSpecializedBonuses(nCurLevel, nProf)
        If nProf <> 0 Then
            sProfDesc = GetWeapProfDesc(oItem.m_sWeapProfType, True) & " = +" & nProf
        End If

        'Weapon
        nWeaponMin = oItem.m_nWeapon_Damage + oItem.m_nWeapon_NumDice
        nWeaponMax = oItem.m_nWeapon_Damage + oItem.m_nWeapon_NumDice * oItem.m_nWeapon_DiceSides
        sWeaponDesc = oItem.ItemName() & " = " & oItem.m_nWeapon_NumDice & "D" & oItem.m_nWeapon_DiceSides
        If oItem.m_nWeapon_Damage > 0 Then
            sWeaponDesc = sWeaponDesc & "+" & oItem.m_nWeapon_Damage
        ElseIf oItem.m_nWeapon_Damage < 0 Then
            sWeaponDesc = sWeaponDesc & oItem.m_nWeapon_Damage
        End If
    End If
    
    'Now total it all up!
    nTotalMin = nWeaponMin + nStrBonus + nProf + nCharMod
    nTotalMax = nWeaponMax + nStrBonus + nProf + nCharMod

        
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Sub

Public Function GetTHACO(nWeapon As Long, _
                         Optional ByRef nBase As Long, Optional ByRef nStatBonus As Long, _
                         Optional ByRef nCharModifier As Long, Optional ByRef nItemBonus As Long, _
                         Optional ByRef sBase As String, Optional ByRef sStat As String, _
                         Optional ByRef sChar As String, Optional ByRef sItem As String) As Long
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetTHACO"
    Call WriteProcStart(sRoutineName)
    
    
    Dim nVal1 As Long, nVal2 As Long
    Dim oTableTHACOs As clsTable
    Dim oItem As clsItem
    Dim nCurLevel As Long
    Dim nTHACOLine1 As Long, nTHACOLine2 As Long
    Dim oClass1 As clsClass, oClass2 As clsClass
    Dim nTemp As Long
    
    sBase = ""
    sStat = ""
    sChar = ""
    sItem = ""
        
    Set oTableTHACOs = g_oTables.GetTableByNode("THACOs")
    
    'If your not even level 1 then we can just assume your entire thaco is 0.
    If nLevel1 = 0 Then
        GetTHACO = 20
    Else
        'Update Current Thaco based upon level
            Set oClass1 = g_oClasses.GetClassByID(nClassID1)
            nTHACOLine1 = oTableTHACOs.FindRow(oClass1.m_sTHACO)
            
            If nTHACOLine1 = -1 Then
                Call Err.Raise(-1, sRoutineName, "Unable to locate THACO1 <" & oClass1.m_sTHACO & "> in THACO online reference table.")
            Else
                nVal1 = oTableTHACOs.Data(nLevel1 + 1, nTHACOLine1)
                
                If IsDualClassed() = True And nLevel2 > 0 Then
                    Set oClass2 = g_oClasses.GetClassByID(nClassID2)
                    nTHACOLine2 = oTableTHACOs.FindRow(oClass2.m_sTHACO)
                    
                    If nTHACOLine2 = -1 Then
                        Call Err.Raise(-1, sRoutineName, "Unable to locate THACO2 <" & oClass2.m_sTHACO & "> in THACO online reference table.")
                    Else
                        nVal2 = oTableTHACOs.Data(nLevel2 + 1, nTHACOLine2)
                    End If
                Else
                    nVal2 = 20
                End If
        
                'Choose the better THACO
                nStatBonus = GetStrTHACOBonus()
                    sStat = "Str of " & Item("nStr") & Item("sExceptionalStr") & " = " & nStatBonus
                
                nCharModifier = nTHACOBonus
                    If Not nCharModifier = 0 Then
                        sChar = "Character Modifier = " & nCharModifier
                    End If
                
                nItemBonus = GetItemTHACOBonus(nWeapon, sItem)
                
                nBase = Min(nVal1, nVal2)
                    If nVal1 <= nVal2 Or IsDualClassed() = False Then
                        sBase = "Level " & nLevel1 & " " & oClass1.m_sClassName & " = " & nVal1
                    Else
                        sBase = "Level " & nLevel2 & " " & oClass2.m_sClassName & " = " & nVal2
                    End If
                
                Set oItem = GetItemWeapon(nWeapon)
                If oItem Is Nothing Then
                    If nWeapon = 1 Then
                        nCharModifier = nCharModifier + nCombatTHACOBonus1
                        If Len(Trim(sChar)) > 0 Then sChar = sChar & ", "
                        sChar = sChar & "THACO Bonus = " & nCombatTHACOBonus1
                    Else
                        nCharModifier = nCharModifier + nCombatTHACOBonus2
                        If Len(Trim(sChar)) > 0 Then sChar = sChar & ", "
                        sChar = sChar & "THACO Bonus = " & nCombatTHACOBonus2
                    End If
                Else
                    '12/11/2002 Chris Hill  Proficiancy level for this weapon!
                    nCurLevel = GetWeapProfLevel(oItem.m_sWeapProfType)
                    Call GetWeapSpecializedBonuses(nCurLevel, , nTemp)
                            
                    nCharModifier = nCharModifier + oItem.m_nTHACOBonus + nTemp
                    If Len(Trim(sChar)) > 0 Then sChar = sChar & ", "
                    sChar = sChar & oItem.ItemName & "(Weap " & nWeapon & "'s Bonus) = " & oItem.m_nTHACOBonus
                            
                    If nTemp > 0 Then
                        sChar = sChar & ", Weap " & nWeapon & " Prof. = " & nTemp
                    End If
                End If
        
                GetTHACO = nBase - nItemBonus - nCharModifier - nStatBonus
            End If
    End If

        
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Public Function IsDualWeaponUser() As Boolean
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".IsDualWeaponUser"
    Call WriteProcStart(sRoutineName)
    
    
    IsDualWeaponUser = (nCombatNumOfAttacks2 > 0)
        
        
    Call WriteProcStop(sRoutineName) ' GetStrDamBonus)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Public Function IsItemWeapon1User() As Boolean
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".IsItemWeapon1User"
    Call WriteProcStart(sRoutineName)
    
    
    IsItemWeapon1User = (nWeaponID1 > 0)
        
        
    Call WriteProcStop(sRoutineName) ' GetStrDamBonus)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Public Function IsItemWeapon2User() As Boolean
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".IsItemWeapon2User"
    Call WriteProcStart(sRoutineName)
    
    
    IsItemWeapon2User = (nWeaponID2 > 0)
        
        
    Call WriteProcStop(sRoutineName) ' GetStrDamBonus)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function

Public Function IsDualClassed() As Boolean
    
    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".IsDualClassed"
    Call WriteProcStart(sRoutineName)
    
    
    IsDualClassed = (nClassID2 > 0)
        
        
    Call WriteProcStop(sRoutineName) ' GetStrDamBonus)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function

'09/06/2002 Steve Esser  (By CJH) Merging.
Public Function Level() As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = Me.Name & ".Level"
    Call WriteProcStart(sRoutineName)

    
    Dim i As Long
    Dim oTableTHACOs As clsTable
    Dim oTableSaves1 As clsTable, oTableSaves2 As clsTable
    Dim nTHACOLine1 As Long, nTHACOLine2 As Long
    Dim sMainMsg(1 To 3) As String
    Dim nTemp As Long
    Dim nSavesLine1 As Long, nSavesLine2 As Long
    Dim oClass1 As clsClass, oClass2 As clsClass
    Dim nNewVal1 As Long, nNewVal2 As Long, nOldVal(2 To 6) As Long
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Validate that the character is ready for leveling
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        If IsNumeric(nExperience1) = False Then
            Level = "Your characters primary class cannot be Auto-Leveled without valid experiance."
            Exit Function
        ElseIf IsNumeric(nExperience2) = False And IsDualClassed() = True Then
            Level = "Your characters secondary class cannot be Auto-Leveled without valid experiance."
            Exit Function
        End If
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Prep the tables we will need like thaco and class info
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        'Get the tables used by all
        Set oTableTHACOs = g_oTables.GetTableByNode("THACOs")
    
        Set oClass1 = g_oClasses.GetClassByID(nClassID1)
        Set oTableSaves1 = g_oTables.GetTableByFile(oClass1.m_sSaves)
        nTHACOLine1 = oTableTHACOs.FindRow(oClass1.m_sTHACO)
    
        'check to see if there is a second class
        If IsDualClassed() = True Then
            Set oClass2 = g_oClasses.GetClassByID(nClassID2)
            oTableSaves2 = g_oTables.GetTableByFile(oClass2.m_sSaves)
            nTHACOLine2 = oTableTHACOs.FindRow(oClass2.m_sTHACO)
        End If
        
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Upgrade classes
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        'Fill out their character sheet for class 1
        sMainMsg(2) = LevelClass(nClassID1, nExperience1, nLevel1, nExpForNextLevel1, "primary")
    
        If IsDualClassed() = True Then
            'Fill out their character sheet for class 2
            sMainMsg(3) = LevelClass(nClassID2, nExperience2, nLevel2, nExpForNextLevel2, "secondary")
        End If
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Upgrade saves
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        nSavesLine1 = oTableSaves1.FindRow(nLevel1)
        If IsDualClassed() = True Then
            nSavesLine2 = oTableSaves2.FindRow(nLevel2)
        End If
        
        nOldVal(2) = nDeath     'Update Para_Pois_Death Save
        nOldVal(3) = nRod       'Update Rod_Staff_Wand Save
        nOldVal(4) = nPoly      'Update Petrification_Polymorph Save
        nOldVal(5) = nBreath    'Update BreathWeapon Save
        nOldVal(6) = nSpell     'Update Spell Save
        
        For i = 2 To 6
            nNewVal1 = CLng(oTableSaves1.Data(i, nSavesLine1))
            nNewVal2 = 20
            If IsDualClassed() = True Then nNewVal2 = CLng(oTableSaves2.Data(i, nSavesLine2))
            If Not Min(nNewVal1, nNewVal2) = nOldVal(i) Then
                sMainMsg(1) = sMainMsg(1) & "     Updated " & oTableSaves1.Data(i, 1) & " save from " & nOldVal(i) & " to " & Trim(CLng(Min(nNewVal1, nNewVal2))) & Chr(13) + Chr(10)
                nOldVal(i) = Min(nNewVal1, nNewVal2)
            End If
        Next i
        
        nDeath = nOldVal(2)     'Update Para_Pois_Death Save
        nRod = nOldVal(3)       'Update Rod_Staff_Wand Save
        nPoly = nOldVal(4)      'Update Petrification_Polymorph Save
        nBreath = nOldVal(5)    'Update BreathWeapon Save
        nSpell = nOldVal(6)      'Update Spell Save
        
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Return the results
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        If Len(Trim(sMainMsg(1))) > 0 Then
            Level = Level & "General: " & vbCrLf & sMainMsg(1)
        End If
        If Len(Trim(sMainMsg(2))) > 0 Then
            Level = Level & "Primary: " & vbCrLf & sMainMsg(2)
        End If
        
'        Level = "General: " & vbCrLf & sMainMsg(1) & _
'                "Primary Class: " & vbCrLf & sMainMsg(2)
        If IsDualClassed() = True Then
            Level = Level & "Secondary Class: " & vbCrLf & sMainMsg(3)
        End If
        
        If Len(Trim(Level)) = 0 Then
            Level = "No Changers Were Needed"
        End If


    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
   
End Function
Private Function LevelClass(ByRef nUpgradeClassID As Long, ByRef nUpgradeExperience As Long, _
                            ByRef nUpgradeLevel As Long, ByRef nUpgradeExpForNextLevel As Long, _
                            Optional sWordToDescribeClass As String = "primary") As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".LevelClass"
    Call WriteProcStart(sRoutineName)


    Dim i As Long
    Dim oClass As clsClass
    Dim oTableLevel As clsTable
    Dim nNewLevel As Long
    Dim nTemp As Long

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Prep the tables we will need like thaco and class info
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        Set oClass = g_oClasses.GetClassByID(nUpgradeClassID)
        Set oTableLevel = g_oTables.GetTableByFile(oClass.m_sLevels)
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Upgrade class
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Find their current level for class
        'Where does this amount of exp put them?
        nNewLevel = -1
        For i = oTableLevel.Rows() To 2 Step -1
            If CLng(oTableLevel.Data(2, i)) < CLng(nUpgradeExperience) Then
                nNewLevel = i - 1
                Exit For
            End If
        Next i
        If nNewLevel = -1 Then nNewLevel = 20
        
    'Tell them what we have found
        If nNewLevel < nUpgradeLevel Then
            LevelClass = "     Your character's " & sWordToDescribeClass & " class is to high to level!"
        ElseIf nNewLevel > nUpgradeLevel Then
            LevelClass = "     Your character's " & sWordToDescribeClass & " class has gained " & nNewLevel - nUpgradeLevel & " level"
            If nNewLevel - nUpgradeLevel > 1 Then LevelClass = LevelClass & "s"
            LevelClass = LevelClass & vbCrLf & "     Hit-Points were !NOT! changed" & vbCrLf
        End If

    'Fill out their character sheet
        'Update Next Level Exp.
        If nUpgradeExpForNextLevel <> oTableLevel.Data(2, Min(nNewLevel + 2, 20)) Then
            LevelClass = LevelClass & "     Next Level Exp. updated from " & nUpgradeExpForNextLevel & " to " & oTableLevel.Data(2, Min(20, nNewLevel + 2)) & Chr(13) + Chr(10)
            nUpgradeExpForNextLevel = oTableLevel.Data(2, Min(nNewLevel + 2, 20))
        End If

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Upgrade our SP
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        nTemp = 0
        If oClass.m_bMageSpells = True Then
            nTemp = GetTableSpellMaximum("MageSpellPoints.DAT", nNewLevel, True, False)
        End If
        If oClass.m_bPriestSpells = True Then
            nTemp = nTemp + GetTableSpellMaximum("PriestSpellPoints.DAT", nNewLevel, False, True)
        End If
        
        If nTemp <> nMaxSP Then
            LevelClass = LevelClass & "     Spell Points updated from " & nMaxSP & " to " & nTemp & Chr(13) + Chr(10)
            nMaxSP = nTemp
        End If

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    'Apply the upgrades
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        nUpgradeLevel = nNewLevel
            
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Private Function GetTableSpellMaximum(sTableName As String, nForLevel As Long, bIntBonus As Boolean, bWisBonus As Boolean) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetTableSpellMaximum"
    Call WriteProcStart(sRoutineName)


    Dim oTableSpellPts As clsTable
    Dim oTableBonusPts As clsTable
    Dim nSpellPtsIndex As Long
    Dim nSpellBonusIndex As Long
    Dim nRow As Long
    
    GetTableSpellMaximum = 0
    Set oTableSpellPts = g_oTables.GetTableByFile(sTableName)
    nRow = oTableSpellPts.FindRow(nForLevel)
    nSpellPtsIndex = oTableSpellPts.FindColumn("Spell Points")
    
    If nRow = -1 Or nSpellPtsIndex = -1 Then
        Call Err.Raise(-1, sRoutineName, "Spell points column not found for level <" & nForLevel & "> and column <Spell Points> in table <" & sTableName & ">")
    Else
        GetTableSpellMaximum = oTableSpellPts.Data(nSpellPtsIndex, nRow)
    End If
    
    'Now find out if they get a bonus
    If bIntBonus = True Then
        Set oTableBonusPts = g_oTables.GetTableByFile("MageIntelliganceSpellPointBonus.DAT")
        nRow = oTableBonusPts.FindRow(nInt)
        nSpellBonusIndex = oTableBonusPts.FindColumn("Bonus Spell Points")
        
        If nRow = -1 Or nSpellBonusIndex = -1 Then
            Call Err.Raise(-1, sRoutineName, "Bonus spell points column not found for Int <" & nInt & "> and column <Bonus Spell Points> in table <MageIntelliganceSpellPointBonus.DAT>")
        Else
            GetTableSpellMaximum = GetTableSpellMaximum + oTableBonusPts.Data(nSpellBonusIndex, nRow)
        End If
    End If
    If bWisBonus = True Then
        Set oTableBonusPts = g_oTables.GetTableByFile("PriestWisdomSpellPointBonus.DAT")
        nRow = oTableBonusPts.FindRow(nInt)
        nSpellBonusIndex = oTableBonusPts.FindColumn(nForLevel)
        
        If nRow = -1 Or nSpellBonusIndex = -1 Then
            Call Err.Raise(-1, sRoutineName, "Bonus spell points column not found for Int <" & nInt & "> and level <" & nForLevel & "> in table <MageIntelliganceSpellPointBonus.DAT>")
        Else
            GetTableSpellMaximum = GetTableSpellMaximum + oTableBonusPts.Data(nSpellBonusIndex, nRow)
        End If
    End If
            
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function MakeNewGUID()

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".MakeNewGUID"
    Call WriteProcStart(sRoutineName)


    Static nLastIDUsed As Long
    nLastIDUsed = nLastIDUsed + 1
    sGUID = "Character" & ":" & Timer & Int((10000 * Rnd) + 1) & "(" & nLastIDUsed & ")"
    
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Private Sub Class_Initialize()

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".Class_Initialize"
    Call WriteProcStart(sRoutineName)


    Dim i As Long
    Call MakeNewGUID
    sState = "UnUsed"
    sCondition = "BM_Token_Normal.bmp"
    
    'Our font choices
    Set m_oFont = g_oFontDefault
    'm_nFontColor = g_nFontDefaultColor
        
'    i = FreeFile
'    Open App.Path & "\MattDebug3.out" For Append As #i
'        Print #i, "Creating character with font " & g_oFontDefault.Name
'    Close #i
    
    '11/15/2002 Chris Hill  By decreasing the max RGB component we avoid neon colors.
    'And actually cuz were kewl lets set the font color to random.
    m_nFontColor = RGB(Int(190 * Rnd), Int(190 * Rnd), Int(190 * Rnd))

    'Vitals
    sName = GenerateNewName
    nHP = Int(Rnd * 100)
    nMaxHP = nHP
    'Combat
    nTHACOBonus = 0
    'Abilities
    nCombatNumDice1 = 1
    nCombatDice1 = 10
    nCombatDamagePlus1 = 0
    nCombatTHACOBonus1 = 0
    nCombatNumOfAttacks1 = 1
    nCombatNumOfAttacksEvery1 = 1
    
    nCombatNumDice2 = 0
    nCombatDice2 = 0
    nCombatDamagePlus2 = 0
    nCombatTHACOBonus2 = 0
    nCombatNumOfAttacks2 = 0
    nCombatNumOfAttacksEvery2 = 0
    
    nStr = 10
    sExceptionalStr = ""
    nDex = 10
    nCon = 10
    nInt = 10
    nWis = 10
    nChr = 10
    nCom = 10
    nPer = 10
    
    nDeath = 10
    nRod = 10
    nPoly = 10
    nBreath = 10
    nSpell = 10
    
    'No longer minion stat only
    sAttackText1 = " Attacks "
    sAttackText2 = " Attacks "
    
    'Minion Only Stats
    bCombatAIOn = True
    bWillAttackParty = True
    bRangedCombat = (Int(Rnd * 2) - 1 = 1)
    nTargetChoice = 0
    
    nClassID1 = 1
    nClassID2 = -1
    nLevel1 = 1
    
    bShowToUsers = True
    bCanLootMinion = False
    bGoesAwayWhenLooted = False
    
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Sub
Public Function RollStat(sStat As String, sStatName As String) As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".RollStat"
    Call WriteProcStart(sRoutineName)


    Dim nRolled As Long
    Dim nItemBonus As Long
    Dim nMRRoll As Long

''Chris
'Dim r As Long
'r = FreeFile
'Open App.Path & "\MattDebug3.out" For Append As #r
'    Print #r, "-----Starting StatRoll (" & sStat & ") for " & sName & "-----"
'Close #r

    nItemBonus = GetItemSaveingThrowsBonus()
    nRolled = Random(1, 20)
    nMRRoll = Random(1, 100)

    If UCase(sStat) = UCase("nStr") Or UCase(sStat) = UCase("nDex") Or UCase(sStat) = UCase("nCon") Or UCase(sStat) = UCase("nInt") Or _
       UCase(sStat) = UCase("nWis") Or UCase(sStat) = UCase("nChr") Or UCase(sStat) = UCase("nCom") Or UCase(sStat) = UCase("nPer") Then
            If nRolled = 1 Then
                RollStat = "[ Critically Makes The " & sStatName & " Check ]"
            ElseIf nRolled = 20 Then
                RollStat = "[ Critically Fails The " & sStatName & " Check ]"
            ElseIf nRolled <= Item(sStat) Then
                RollStat = "[ Made The " & sStatName & " Check By " & Item(sStat) - nRolled & " ]"
            Else
                RollStat = "[ Missed The " & sStatName & " Check By " & nRolled - Item(sStat) & " ]"
            End If
    'This means its a save
    Else
        If nMRRoll <= nMR And nMR > 0 And UCase(sStat) = UCase("nSpell") Then
            RollStat = "[ Made The " & sStatName & " Roll Because Of Magic Resistance ]"
        Else
            If nRolled = 20 Then
                RollStat = "[ Critically Makes The " & sStatName & " Roll ]"
            ElseIf nRolled = 1 Then
                RollStat = "[ Critically Failes The " & sStatName & " Roll ]"
            ElseIf nRolled + nItemBonus >= Item(sStat) Then
                RollStat = "[ Made The " & sStatName & " Roll By " & (nRolled + nItemBonus) - Item(sStat) & " ]"
            Else
                RollStat = "[ Missed The " & sStatName & " Roll By " & Item(sStat) - (nRolled + nItemBonus) & " ]"
            End If
        End If
    End If
    
''Chris
'r = FreeFile
'Open App.Path & "\MattDebug3.out" For Append As #r
'    Print #r, "     ItemBonus:" & nItemBonus & "  Rolled:" & nRolled & "  MRRoll:" & nMRRoll & "  Stat:" & Item(sStat) & "  Msg:" & RollStat
'Close #r

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function IsCaster() As Boolean

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".IsCaster"
    Call WriteProcStart(sRoutineName)


    IsCaster = (nMaxSP > 0)

    
    Call WriteProcStop(sRoutineName) ' IsCaster)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function AI_EvalStrength() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".AI_EvalStrength"
    Call WriteProcStart(sRoutineName)


    If nLevel2 > 0 Then
        AI_EvalStrength = ((nLevel1 + nLevel2) / 2) * 100 + AC * 10 + nStr + nHP
    Else
        AI_EvalStrength = nLevel1 * 100 + AC * 10 + nStr + nHP
    End If

    
    Call WriteProcStop(sRoutineName) ' AI_EvalStrength)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function AI_SelectCombatTarget(colAllChars As Collection) As clsCharacter

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".AI_SelectCombatTarget"
    Call WriteProcStart(sRoutineName)

    
    Dim oLoopMinion As clsCharacter
    Dim oCurMin As clsCharacter

    'If the AI is off... don't do this!!
    If bCombatAIOn = False Then
        Set AI_SelectCombatTarget = Nothing
        Exit Function
    End If
    
    'Step through all the minions in search of someone to attack
    For Each oLoopMinion In colAllChars
        'Make sure it isn't me... I won't attack myself.  Check to see who I WILL attack.
        If Not oLoopMinion.sGUID = sGUID And Not oLoopMinion.bWillAttackParty = bWillAttackParty Then
            
            If oCurMin Is Nothing Then
                Set oCurMin = oLoopMinion
            Else
                'Closest
                If nTargetChoice = 0 Then
                    If Distance(oCurMin.nX, oCurMin.nY, nX, nY) > Distance(oLoopMinion.nX, oLoopMinion.nY, nX, nY) Then
                        Set oCurMin = oLoopMinion
                    End If
                'Weakest
                ElseIf nTargetChoice = 1 Then
                    If oCurMin.AI_EvalStrength() > oLoopMinion.AI_EvalStrength Then
                        Set oCurMin = oLoopMinion
                    End If
                'Strongest
                ElseIf nTargetChoice = 2 Then
                    If oCurMin.AI_EvalStrength() < oLoopMinion.AI_EvalStrength Then
                        Set oCurMin = oLoopMinion
                    End If
                'Caster
                Else
                    If oCurMin.nSP < oLoopMinion.nSP Then
                        Set oCurMin = oLoopMinion
                    End If
                End If
            End If
        
        End If
    Next oLoopMinion
    
    'Return our result
    Set AI_SelectCombatTarget = oCurMin
    
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Sub AI_Attack(oTarget As clsCharacter, nPixelsPerFoot As Long, nRound As Long)

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".AI_Attack"
    Call WriteProcStart(sRoutineName)

    
    Dim i As Long
    Dim nOldDir As Long
    Dim nDistInFeet As Long
    Dim nAngle As Double
    Dim nTravelDist As Long
    Dim nDamage As Long, nACHit As Long, nTHACORolled As Long
        
    nDistInFeet = Distance(oTarget.nX, oTarget.nY, nX, nY) / nPixelsPerFoot
    nAngle = CalcAngle(nX, nY, oTarget.nX, oTarget.nY) + 90
    'Figure out how far we need to travel
    If bRangedCombat = True Then
        nTravelDist = nDistInFeet - 150
    Else
        nTravelDist = nDistInFeet - 3
    End If
    'Top it out because their is only so far we can go
    nTravelDist = Max(nTravelDist, 0)
    nTravelDist = Min(nTravelDist, 150)
       
    'Now lets move closer...
    nX = nX + DegCos(nAngle) * (nTravelDist * nPixelsPerFoot)
    nY = nY + DegSin(nAngle) * (nTravelDist * nPixelsPerFoot)
    'Now lets figure out which direction we need to be facing
    nOldDir = nDirection
    nDirection = Fix((nAngle + 45 + 90) / 90) Mod 4
    
    'Now announce our change to the world!!
    If nTravelDist > 0 Or Not nOldDir = nDirection Then
        Call MakeProcessSendMsg(g_cUpdateBMPeiceCode, sGUID, sCondition, nDirection, nX, nY, "Normal")
    End If
    
    'Now lets make our attack!
    For i = 1 To GetNumAttacksThisRound(1, nRound) 'WeaponNum)
        Call RollAttack(nACHit, nDamage, nTHACORolled, 1)
        Call MakeProcessSendMsg(g_cWhenMinionsAttackCode, sGUID, oTarget.sGUID, nDamage, nACHit, AttackText1)
    Next i
    
    If nCombatNumOfAttacks2 > 0 Then
        'Now lets make our attack!
        For i = 1 To GetNumAttacksThisRound(2, nRound)
            Call RollAttack(nACHit, nDamage, nTHACORolled, 2)
            Call MakeProcessSendMsg(g_cWhenMinionsAttackCode, sGUID, oTarget.sGUID, nDamage, nACHit, AttackText2)
        Next i
    End If
    
    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Sub
'This is one based.
Public Function GetNumAttacksThisRound(nWeaponNum As Long, nRound As Long) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetNumAttacksThisRound"
    Call WriteProcStart(sRoutineName)

    
    Dim nRow As Long, nCol As Long
    Dim nWholeAttacks As Long
    Dim nPartOfAttacks As Long
    Dim sItemWeapProf As String
    Dim nTemp As Long
    Dim oItem As clsItem
    Dim oTableSpecial As clsTable
    Dim oTableWeapProf As clsTable
    Dim sSpecialtyType As String
    Dim nLevel As Long
    
    nLevel = nLevel1
    If IsDualClassed() = True Then
        nLevel = Max(nLevel1, nLevel2)
    End If
    
    Set oTableSpecial = g_oTables.GetTableByFile("Table35_SpecialistAttacksPerRound.DAT")
    Set oTableWeapProf = g_oTables.GetTableByFile("Table37_WeaponProficiencies.DAT")
    
    If nWeaponNum = 2 Then
   
        '12/11/2002 Chris Hill  Does this weapon give us more attacks a round?
        If IsItemWeapon2User() = True Then
            Set oItem = m_oItems.GetItemByID(nWeaponID2)
            If oItem Is Nothing Then
                Call Err.Raise(-1, sRoutineName, "Unable to find item currently assigned as secondary weapon.")
            End If
        Else
            nWholeAttacks = Fix(nCombatNumOfAttacks2 / nCombatNumOfAttacksEvery2)
            nPartOfAttacks = nCombatNumOfAttacks2 - (nWholeAttacks * nCombatNumOfAttacksEvery2)
        
            GetNumAttacksThisRound = nWholeAttacks
            If nRound Mod nCombatNumOfAttacksEvery2 < nPartOfAttacks Then
                GetNumAttacksThisRound = nWholeAttacks + 1
            End If
        End If
    
    Else
       
        '12/11/2002 Chris Hill  Does this weapon give us more attacks a round?
        If IsItemWeapon1User() = True Then
            Set oItem = m_oItems.GetItemByID(nWeaponID1)
            If oItem Is Nothing Then
                Call Err.Raise(-1, sRoutineName, "Unable to find item currently assigned as primary weapon.")
            End If
        Else
            nWholeAttacks = Fix(nCombatNumOfAttacks1 / nCombatNumOfAttacksEvery1)
            nPartOfAttacks = nCombatNumOfAttacks1 - (nWholeAttacks * nCombatNumOfAttacksEvery1)
            
            GetNumAttacksThisRound = nWholeAttacks
            If nRound Mod nCombatNumOfAttacksEvery1 < nPartOfAttacks Then
                GetNumAttacksThisRound = nWholeAttacks + 1
            End If
        End If
    End If
    
    '12/11/2002 Chris Hill  Calculate how many attacks a round we now get
    If Not oItem Is Nothing Then
        sItemWeapProf = oItem.m_sWeapProfType

        nRow = oTableWeapProf.FindRow(sItemWeapProf)
        If nRow = -1 Then
            GetNumAttacksThisRound = 1
        Else
            sSpecialtyType = oTableWeapProf.Data(2, nRow)
            nCol = oTableSpecial.FindColumn(sSpecialtyType)
            nRow = oTableSpecial.FindRow(nLevel)
            
            If nCol = -1 Then
                GetNumAttacksThisRound = 1
                Call Err.Raise(-1, sRoutineName, "Unable to find specialty type " & sSpecialtyType & " to determine the number of attacks.")
            ElseIf nRow = -1 Then
                GetNumAttacksThisRound = 1
                Call Err.Raise(-1, sRoutineName, "Unable to find level " & nLevel & " to determine the number of attacks.")
            Else
                If GetWeapProfLevel(sItemWeapProf) > 1 Then
                    nWholeAttacks = Fix(Mid(oTableSpecial.Data(nCol, nRow), 1, 1) / Mid(oTableSpecial.Data(nCol, nRow), 3, 1))
                    nPartOfAttacks = Mid(oTableSpecial.Data(nCol, nRow), 1, 1) - (nWholeAttacks * Mid(oTableSpecial.Data(nCol, nRow), 3, 1))
                Else
                    nWholeAttacks = 1
                    nPartOfAttacks = 0
                End If
                                
                GetNumAttacksThisRound = nWholeAttacks
                '08/04/2004 Chris Hill  Fixed it so that round 1 defaults to 2 attacks instead of 1.
                If (nRound + 1) Mod Mid(oTableSpecial.Data(nCol, nRow), 3, 1) < nPartOfAttacks Then
                    GetNumAttacksThisRound = nWholeAttacks + 1
                End If
            End If
        End If
    End If
    
    
    Call WriteProcStop(sRoutineName) ' GetNumAttacksThisRound)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function

Public Function ACHitToText(nACHit As Long, nDmg As Long, nTHACOHit As Long) As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".ACHitToText"
    Call WriteProcStart(sRoutineName)


    Dim sAC As String
    Dim i As Long
    
    If nACHit < -10 Then
        sAC = "-10 ( " + CStr(nACHit) + " )"
    'ElseIf nACHit > 10 Then
    '    sAC = "10 ( " + CStr(nACHit) + " )"
    Else
        sAC = nACHit
    End If

    If nTHACOHit >= CritOn() Then
        ACHitToText = "[ Critically Hits AC " & sAC & " for " & nDmg & " damage ]"
    ElseIf nTHACOHit = 1 Then
        ACHitToText = "[ Critically Fails The attack and "
        i = Int((6 * Rnd) + 1)
        If i = 1 Then
            ACHitToText = ACHitToText & "breaks the weapon ]"
        ElseIf i = 2 Then
            ACHitToText = ACHitToText & "throws the weapon " & Int((20 * Rnd) + 1) & " yards ]"
        ElseIf i = 3 Then
            ACHitToText = ACHitToText & "throws the weapon " & Int((10 * Rnd) + 1) & " yards ]"
        ElseIf i = 4 Then
            ACHitToText = ACHitToText & "misses ]"
        ElseIf i = 5 Then
            ACHitToText = ACHitToText & "hits themself for " & nDmg - GetStrDamBonus() & " ]"
        Else
            ACHitToText = ACHitToText & "hits a party member for " & nDmg - GetStrDamBonus() & " ]"
        End If
    Else
        ACHitToText = "[ Hits AC " & sAC & " for " & nDmg & " damage ]"
    End If
    
    
    Call WriteProcStop(sRoutineName) ' ACHitToText)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Private Function CritOn() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".CritOn"
    Call WriteProcStart(sRoutineName)

    
    Dim oClass As clsClass
    Dim nCritOn1 As Long, nCritOn2 As Long
    Dim nCritOnProf As Long
    
    Set oClass = g_oClasses.GetClassByID(nClassID1)
    nCritOn1 = oClass.m_nCritOn
    
    If IsDualClassed() = True Then
        Set oClass = g_oClasses.GetClassByID(nClassID2)
        nCritOn2 = oClass.m_nCritOn
    Else
        nCritOn2 = 20
    End If
    
    '12/17/2002 Chris Hill  Factor the single weapon prof into the mix
    If GetWeapProfLevel(g_cSINGLEWEAPPROF) > 0 And IsDualWeaponUser() = False And IsItemWeapon2User() = False Then
        nCritOnProf = 19
    Else
        nCritOnProf = 20
    End If
    
    CritOn = Min(nCritOn1, nCritOn2)
    CritOn = Min(CritOn, nCritOnProf)

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Public Function RollAttack(ByRef nACHit As Long, ByRef nDamage As Long, ByRef nTHACORolled As Long, nWeaponNum As Long, _
                        Optional ByRef sAttackBlurb) As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".RollAttack"
    Call WriteProcStart(sRoutineName)

    
    Dim oItem As clsItem
    Dim i As Long
    Dim nNumDice As Long, nDiceSide As Long, nDamageBonus As Long
    Dim nCurLevel As Long
    
    If nWeaponNum = 2 Then
        'For Weapon 2
        If IsItemWeapon2User() = True Then
            Set oItem = m_oItems.GetItemByID(nWeaponID2)
            If oItem Is Nothing Then
                Call Err.Raise(-1, sRoutineName, "Unable to find item currently assigned as secondary weapon.")
            Else
                '12/11/2002 Chris Hill  Proficiancy level for this weapon!
                nCurLevel = GetWeapProfLevel(oItem.m_sWeapProfType)
                Call GetWeapSpecializedBonuses(nCurLevel, nDamageBonus)
            
                nDamageBonus = oItem.m_nWeapon_Damage
                nDiceSide = oItem.m_nWeapon_DiceSides
                nNumDice = oItem.m_nWeapon_NumDice
                sAttackBlurb = oItem.m_sWeapon_Text
            End If
        Else
            nDamageBonus = nCombatDamagePlus2
            nDiceSide = nCombatDice2
            nNumDice = nCombatNumDice2
            sAttackBlurb = AttackText2
        End If
    Else
        If IsItemWeapon1User() = True Then
            Set oItem = m_oItems.GetItemByID(nWeaponID1)
            If oItem Is Nothing Then
                Call Err.Raise(-1, sRoutineName, "Unable to find item currently assigned as primary weapon.")
            Else
                '12/11/2002 Chris Hill  Proficiancy level for this weapon!
                nCurLevel = GetWeapProfLevel(oItem.m_sWeapProfType)
                Call GetWeapSpecializedBonuses(nCurLevel, nDamageBonus)
                
                nDamageBonus = nDamageBonus + oItem.m_nWeapon_Damage
                nDiceSide = oItem.m_nWeapon_DiceSides
                nNumDice = oItem.m_nWeapon_NumDice
                sAttackBlurb = oItem.m_sWeapon_Text
            End If
        Else
            nDamageBonus = nCombatDamagePlus1
            nDiceSide = nCombatDice1
            nNumDice = nCombatNumDice1
            sAttackBlurb = AttackText1
        End If
    End If
    
    'Add a space onto the end of our attack message if we need to
    If Len(sAttackBlurb) > 0 Then
        sAttackBlurb = Trim(sAttackBlurb) & " "
    End If
    
    nTHACORolled = Int((20 * Rnd) + 1)
    nACHit = GetTHACO(nWeaponNum) - nTHACORolled
    'Calculate the base damage For Weapon
    nDamage = nDamageBonus + GetStrDamBonus() + nCombatDamageBonus

    For i = 1 To nNumDice
        nDamage = nDamage + Int((nDiceSide * Rnd) + 1)
    Next i
    
    'They rolled a 20!  Well don't double it but add the max into it too.
    If nTHACORolled >= CritOn() Then
        nDamage = nDamage + (nDiceSide * nNumDice) + nDamageBonus + GetStrDamBonus() + nCombatDamageBonus
    End If
    
    'And last but not least add in the text that would be generated, in case anyone cares
    RollAttack = ACHitToText(nACHit, nDamage, nTHACORolled)

    
    Call WriteProcStop(sRoutineName) ' nACHit, nDamage)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

Public Function CharacterAttack(nRound As Long) As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".CharacterAttack"
    Call WriteProcStart(sRoutineName)
    
    
    Dim i As Long
    Dim nACHit As Long, nDamage As Long, nTHACORolled As Long
    Dim sAttackMessage As String
    Dim nNumAttacks As Long
    
    nLastRoundAttackedFor = nRound
    
    'Roll attacks for Weapon 1
    nNumAttacks = GetNumAttacksThisRound(1, nRound)
    For i = 1 To nNumAttacks
        'Calculate the attack
        Call RollAttack(nACHit, nDamage, nTHACORolled, 1, sAttackMessage)

        CharacterAttack = CharacterAttack & sAttackMessage & ACHitToText(nACHit, nDamage, nTHACORolled)
        If i < GetNumAttacksThisRound(1, nRound) Or nCombatNumOfAttacks2 > 0 Then
            CharacterAttack = CharacterAttack & Chr(13) + Chr(10)
        End If
    Next i
   
    If nCombatNumOfAttacks2 > 0 Then
        nNumAttacks = GetNumAttacksThisRound(2, nRound)
        For i = 1 To nNumAttacks
            'Calculate the attack
            Call RollAttack(nACHit, nDamage, nTHACORolled, 2, sAttackMessage)

            CharacterAttack = CharacterAttack & sAttackMessage & ACHitToText(nACHit, nDamage, nTHACORolled)
            If i < GetNumAttacksThisRound(2, nRound) Then
                CharacterAttack = CharacterAttack & Chr(13) + Chr(10)
            End If
        Next i
    End If


    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function GetNumMaxNonWeapProf() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetNumMaxNonWeapProf"
    Call WriteProcStart(sRoutineName)


    Dim nRow As Long
    Dim oTableProfSlots As clsTable
    Dim oClass1 As clsClass, oClass2 As clsClass
    Dim nSlotsClass1 As Long, nSlotsClass2 As Long

    Set oTableProfSlots = g_oTables.GetTableByFile("Table34_ProficiencySlots.DAT")

    Set oClass1 = g_oClasses.GetClassByID(nClassID1)
    nRow = oTableProfSlots.FindRow(oClass1.m_sTHACO)
    
    If nRow = -1 Then
        Call Err.Raise(-1, sRoutineName, "Unable to locate THACO1 <" & oClass1.m_sTHACO & "> in THACO online reference table.")
    Else
        nSlotsClass1 = oTableProfSlots.Data(5, nRow) + CLng(nLevel1 / oTableProfSlots.Data(6, nRow))
        
        nSlotsClass2 = 0
        If IsDualClassed() = True Then
            Set oClass2 = g_oClasses.GetClassByID(nClassID2)
            nRow = oTableProfSlots.FindRow(oClass2.m_sTHACO)
            nSlotsClass2 = oTableProfSlots.Data(5, nRow) + CLng(nLevel2 / oTableProfSlots.Data(6, nRow))
        End If
        
        GetNumMaxNonWeapProf = Max(nSlotsClass1, nSlotsClass2)
    End If

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function GetNumMaxWeapProf() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetNumMaxWeapProf"
    Call WriteProcStart(sRoutineName)


    Dim nRow As Long
    Dim oTableProfSlots As clsTable
    Dim oClass1 As clsClass, oClass2 As clsClass
    Dim nSlotsClass1 As Long, nSlotsClass2 As Long

    Set oTableProfSlots = g_oTables.GetTableByFile("Table34_ProficiencySlots.DAT")

    Set oClass1 = g_oClasses.GetClassByID(nClassID1)
    nRow = oTableProfSlots.FindRow(oClass1.m_sTHACO)
    
    If nRow = -1 Then
        Call Err.Raise(-1, sRoutineName, "Unable to locate THACO1 <" & oClass1.m_sTHACO & "> in THACO online reference table.")
    Else
        nSlotsClass1 = oTableProfSlots.Data(2, nRow) + CLng(nLevel1 / oTableProfSlots.Data(3, nRow))
    
        nSlotsClass2 = 0
        If IsDualClassed() = True Then
            Set oClass2 = g_oClasses.GetClassByID(nClassID2)
            nRow = oTableProfSlots.FindRow(oClass2.m_sTHACO)
            nSlotsClass2 = oTableProfSlots.Data(2, nRow) + CLng(nLevel2 / oTableProfSlots.Data(3, nRow))
        End If
    
        GetNumMaxWeapProf = Max(nSlotsClass1, nSlotsClass2)
    End If

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

Public Function GetNumUsedNonWeapProf() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetNumUsedNonWeapProf"
    Call WriteProcStart(sRoutineName)


    Dim bFound As Boolean
    Dim nSlots As Long
    Dim saProf() As String
    Dim saTypes() As String
    Dim sTypes As String
    Dim sTypesICanAccess As String
    Dim nTotalSlotsUsed As Long
    Dim oClass1 As clsClass, oClass2 As clsClass
    Dim i As Long, t As Long

    Set oClass1 = g_oClasses.GetClassByID(nClassID1)
    sTypesICanAccess = oClass1.m_sProficiencies
    
    If IsDualClassed() = True Then
        Set oClass2 = g_oClasses.GetClassByID(nClassID2)
        sTypesICanAccess = sTypesICanAccess & oClass2.m_sProficiencies
    End If

    saProf = Split(sProficiencies, g_cPROFSEPERATOR)
    
    For i = 0 To UBound(saProf) - 1
    
        If FindProf(saProf(i), sTypes, nSlots) = True Then
            'Now find out if we have that proficiency type
            bFound = False
            saTypes = Split(sTypes, g_cPROFSEPERATOR)
            For t = 0 To UBound(saTypes) - 1
                '08/21/2003 Chris Hill  Fixed it so that custom classes calculate proficies corrctly.
                If InStr(1, sTypesICanAccess, saTypes(t) & ",") > 0 Or InStr(1, sTypesICanAccess, saTypes(t) & g_cPROFSEPERATOR) > 0 Then
                    bFound = True
                    Exit For
                End If
            Next t
            If bFound = True Then
                nTotalSlotsUsed = nTotalSlotsUsed + nSlots
            Else
                nTotalSlotsUsed = nTotalSlotsUsed + nSlots * 2
            End If
        Else
            Call Err.Raise(-1, sRoutineName, "Unable to find proficiency <" & saProf(i) & "> in proficiencies data file")
        End If
    
    Next i
    
    GetNumUsedNonWeapProf = nTotalSlotsUsed

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function GetNumUsedWeapProf() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetNumUsedWeapProf"
    Call WriteProcStart(sRoutineName)


    Dim i As Long
    Dim saWeapProf() As String
    Dim nWhere As Long

    saWeapProf = Split(sWeapProf, g_cPROFSEPERATOR)
    For i = 0 To UBound(saWeapProf) - 1
        nWhere = InStr(1, saWeapProf(i), g_cPROFNUMSEPERATOR)
        If nWhere > 0 Then
            GetNumUsedWeapProf = GetNumUsedWeapProf + Mid(saWeapProf(i), nWhere + Len(g_cPROFNUMSEPERATOR))
        End If
    Next i

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

Private Function FindProf(sProf As String, ByRef sTypes As String, ByRef nSlots As Long) As Boolean

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".FindProf"
    Call WriteProcStart(sRoutineName)


    Dim nRow As Long
    Dim oTableProfAvail As clsTable
    Dim oClass1 As clsClass, oClass2 As clsClass
    Dim nSlotsClass1 As Long, nSlotsClass2 As Long

    sTypes = ""
    nSlots = -1
    FindProf = False
    Set oTableProfAvail = g_oTables.GetTableByFile("Table37_Proficiencies.DAT")

    For nRow = 2 To oTableProfAvail.Rows()
        If oTableProfAvail.Data(2, nRow) = sProf Then
            sTypes = sTypes & oTableProfAvail.Data(1, nRow) & g_cPROFSEPERATOR
            If nSlots = -1 Or oTableProfAvail.Data(3, nRow) < nSlots Then
                nSlots = oTableProfAvail.Data(3, nRow)
            End If
            FindProf = True
        End If
    Next nRow

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

Public Function GetNumMaxLanguages() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetNumMaxLanguages"
    Call WriteProcStart(sRoutineName)


    Dim oTableLang As clsTable
    Dim nRow As Long, nCol As Long

    Set oTableLang = g_oTables.GetTableByFile("Intelligence.DAT")
    nRow = oTableLang.FindRow(nInt)
    nCol = oTableLang.FindColumn("# of Lang.")
    
    If IsNumeric(oTableLang.Data(nCol, nRow)) = False Then
        GetNumMaxLanguages = 0
    Else
        GetNumMaxLanguages = oTableLang.Data(nCol, nRow)
    End If

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function GetNumUsedLanguages() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetNumUsedLanguages"
    Call WriteProcStart(sRoutineName)


    Dim saLang() As String
    
    If Len(sLanguages) = 0 Then
        GetNumUsedLanguages = 0
    Else
        saLang = Split(sLanguages, g_cLANGSEPERATOR)
        GetNumUsedLanguages = UBound(saLang)
    End If

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function GetItemSavesBonus(Optional ByRef sItemBonus As String) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetItemSavesBonus"
    Call WriteProcStart(sRoutineName)


    Dim oLoopItem As clsItem
    
    GetItemSavesBonus = 0
    
    For Each oLoopItem In m_oItems.Col()
        If oLoopItem.m_bActive = True And oLoopItem.m_nSavingThrowBonus > 0 And oLoopItem.m_bIdentified = True And _
           (oLoopItem.IsWeapon() = False Or (nWeaponID1 = oLoopItem.m_nItemID Or nWeaponID2 = oLoopItem.m_nItemID)) Then
                GetItemSavesBonus = GetItemSavesBonus + oLoopItem.m_nSavingThrowBonus
                If Len(sItemBonus) > 0 Then
                    sItemBonus = sItemBonus & ", "
                End If
                sItemBonus = sItemBonus & oLoopItem.ItemName & " = " & oLoopItem.m_nSavingThrowBonus
        End If
    Next oLoopItem

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

Public Function GetItemACBonus(Optional ByRef sItemBonus As String) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetItemACBonus"
    Call WriteProcStart(sRoutineName)


    Dim oLoopItem As clsItem
    
    GetItemACBonus = 0
    
    For Each oLoopItem In m_oItems.Col()
        If oLoopItem.m_bActive = True And oLoopItem.m_nACBonus > 0 And oLoopItem.m_bIdentified = True And _
           (oLoopItem.IsWeapon() = False Or (nWeaponID1 = oLoopItem.m_nItemID Or nWeaponID2 = oLoopItem.m_nItemID)) Then
                GetItemACBonus = GetItemACBonus + oLoopItem.m_nACBonus
                If Len(sItemBonus) > 0 Then
                    sItemBonus = sItemBonus & ", "
                End If
                sItemBonus = sItemBonus & oLoopItem.ItemName & " = " & oLoopItem.m_nACBonus
        End If
    Next oLoopItem

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function GetItemInitBonus(Optional ByRef sItemBonus As String) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetItemInitBonus"
    Call WriteProcStart(sRoutineName)


    Dim oLoopItem As clsItem
    
    GetItemInitBonus = 0
    
    For Each oLoopItem In m_oItems.Col()
        If oLoopItem.m_bActive = True And oLoopItem.m_nInitBonus > 0 And oLoopItem.m_bIdentified = True And _
           (oLoopItem.IsWeapon() = False Or (nWeaponID1 = oLoopItem.m_nItemID Or nWeaponID2 = oLoopItem.m_nItemID)) Then
                GetItemInitBonus = GetItemInitBonus - oLoopItem.m_nInitBonus
                If Len(sItemBonus) > 0 Then
                    sItemBonus = sItemBonus & ", "
                End If
                sItemBonus = sItemBonus & oLoopItem.ItemName & " = " & oLoopItem.m_nInitBonus
        End If
    Next oLoopItem

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function GetItemSaveingThrowsBonus() As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetItemSaveingThrowsBonus"
    Call WriteProcStart(sRoutineName)


    Dim oLoopItem As clsItem
    
    GetItemSaveingThrowsBonus = 0
    
    For Each oLoopItem In m_oItems.Col()
        If oLoopItem.m_bActive = True And oLoopItem.m_bIdentified = True And oLoopItem.m_nSavingThrowBonus > 0 And _
           (oLoopItem.IsWeapon() = False Or (nWeaponID1 = oLoopItem.m_nItemID Or nWeaponID2 = oLoopItem.m_nItemID)) Then
                GetItemSaveingThrowsBonus = GetItemSaveingThrowsBonus + oLoopItem.m_nSavingThrowBonus
        End If
    Next oLoopItem

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function GetItemTHACOBonus(nWeapon As Long, Optional ByRef sItemBonus As String) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetItemTHACOBonus"
    Call WriteProcStart(sRoutineName)


    Dim oLoopItem As clsItem
    
    GetItemTHACOBonus = 0
    
    For Each oLoopItem In m_oItems.Col()
        '04/08/2003 Chris Hill  You nolonger get bonuses for weapons.
        If oLoopItem.m_bActive = True And Not oLoopItem.m_nTHACOBonus() = 0 And oLoopItem.IsWeapon() = False And oLoopItem.m_bIdentified = True And _
           (oLoopItem.IsWeapon() = False Or (nWeaponID1 = oLoopItem.m_nItemID Or nWeaponID2 = oLoopItem.m_nItemID)) Then
                GetItemTHACOBonus = GetItemTHACOBonus + oLoopItem.m_nTHACOBonus()
        
                If Len(sItemBonus) > 0 Then
                    sItemBonus = sItemBonus & ", "
                End If
                sItemBonus = sItemBonus & oLoopItem.ItemName & " = " & oLoopItem.m_nTHACOBonus()
        End If
    Next oLoopItem

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

Public Function GetWeapProfLevel(sFindWeapProf As String) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetWeapProfLevel"
    Call WriteProcStart(sRoutineName)


    Dim i As Long, n As Long
    Dim nWhere As Long
    Dim bFound As Boolean
    Dim sTemp As String
    Dim saWeapProf() As String
    
    saWeapProf = Split(sWeapProf, g_cPROFSEPERATOR)
    GetWeapProfLevel = 0
        
    'Find this weap-prof
    For n = 0 To UBound(saWeapProf, 1) - 1
        sTemp = Left(saWeapProf(n), InStr(1, saWeapProf(n), g_cPROFNUMSEPERATOR) - 1)
        If sTemp = sFindWeapProf Then
            GetWeapProfLevel = Mid(saWeapProf(n), InStr(1, saWeapProf(n), g_cPROFNUMSEPERATOR) + Len(g_cPROFNUMSEPERATOR))
            Exit Function
        End If
    Next n

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function AddWeapProfLevel(sFindWeapProf As String) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".AddWeapProfLevel"
    Call WriteProcStart(sRoutineName)


    Dim nCurLevel As Long
    
    nCurLevel = GetWeapProfLevel(sFindWeapProf)
    
    If nCurLevel > 0 Then
        Call CompletelyRemoveWeapProf(sFindWeapProf)
    End If
    
    nCurLevel = nCurLevel + 1
    '12/17/2002 Chris Hill  Factor in the new proficiancy type
    If sFindWeapProf = g_cSINGLEWEAPPROF Then
        nCurLevel = 1
    End If
    
    sWeapProf = sWeapProf & sFindWeapProf & g_cPROFNUMSEPERATOR & Min(nCurLevel, 4) & g_cPROFSEPERATOR
    
    AddWeapProfLevel = Min(nCurLevel + 1, 4)
    
   
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function
Public Function SubtractWeapProfLevel(sFindWeapProf As String) As Long

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".SubtractWeapProfLevel"
    Call WriteProcStart(sRoutineName)


    Dim nCurLevel As Long
    
    nCurLevel = GetWeapProfLevel(sFindWeapProf)
    
    If nCurLevel > 0 Then
        Call CompletelyRemoveWeapProf(sFindWeapProf)
    End If
    If Max(nCurLevel - 1, 0) > 0 Then
        sWeapProf = sWeapProf & sFindWeapProf & g_cPROFNUMSEPERATOR & Max(nCurLevel - 1, 0) & g_cPROFSEPERATOR
    End If
    
    SubtractWeapProfLevel = Max(nCurLevel - 1, 0)
    
   
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Function

Public Sub CompletelyRemoveWeapProf(sFindWeapProf As String)

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".CompletelyRemoveWeapProf"
    Call WriteProcStart(sRoutineName)


    Dim i As Long
    Dim sTemp As String
    Dim saWeapProf() As String
   
    saWeapProf = Split(sWeapProf, g_cPROFSEPERATOR)
    sWeapProf = ""
        
    'Find this weap-prof
    For i = 0 To UBound(saWeapProf, 1) - 1
        sTemp = Left(saWeapProf(i), InStr(1, saWeapProf(i), g_cPROFNUMSEPERATOR) - 1)
        If Not sTemp = sFindWeapProf Then
            sWeapProf = sWeapProf & saWeapProf(i) & g_cPROFSEPERATOR
        End If
    Next i

    
    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Sub
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount
    
End Sub
Public Function GetWeapProfDesc(sWeapProf As String, Optional bOnlySpecializationLevelName As Boolean = False) As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".GetWeapProfDesc"
    Call WriteProcStart(sRoutineName)

    
    Dim nLevel As Long
    Dim oTableSpecial As clsTable
    Dim nRow As Long
    
    nLevel = GetWeapProfLevel(sWeapProf)
    Set oTableSpecial = g_oTables.GetTableByFile("SpecialistBonuses.DAT")
    nRow = oTableSpecial.FindRow(nLevel)
    
    If bOnlySpecializationLevelName = False Then
        GetWeapProfDesc = Left(sWeapProf & String(50, " "), 50)
        GetWeapProfDesc = GetWeapProfDesc & "(" & oTableSpecial.Data(2, nRow) & ")"
    Else
        GetWeapProfDesc = sWeapProf & " " & oTableSpecial.Data(2, nRow)
    End If
    

    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Function
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Function
Public Property Get IsMinion() As Boolean

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".IsMinion"
    Call WriteProcStart(sRoutineName)

    
    If nCharID < 0 Then
        IsMinion = True
    Else
        IsMinion = False
    End If
    

    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Property
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Property
Public Property Get AttackText1() As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".AttackText1"
    Call WriteProcStart(sRoutineName)

    
    If Len(sAttackText1) = 0 Then
        AttackText1 = " Attacks "
    Else
        AttackText1 = sAttackText1
    End If
    

    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Property
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Property

Public Property Get AttackText2() As String

    On Error GoTo ErrorHandler
    Dim sRoutineName As String
    sRoutineName = m_cNAME & ".AttackText2"
    Call WriteProcStart(sRoutineName)

    
    If Len(sAttackText2) = 0 Then
        AttackText2 = " Attacks "
    Else
        '07/19/2004 Chris Hill  This was in error, it should be referencing attack type 2.
        AttackText2 = sAttackText2
    End If
    

    Call WriteProcStop(sRoutineName)
    'Exit the sub... on an error we will jump over this function
    Exit Property
ErrorHandler:
    Call LogErrorToFile(sRoutineName & "." & Err.Description): Call g_oErrors.HandleErrorWithSource(Err, sRoutineName): Call g_oErrors.DisplayErrorCount

End Property
