Quote:
Originally Posted by Secrets
It's around 9304 spells, and any spell after a certain number doesn't get loaded.
It's one of those things in the titanium client that can't be tricked, sadly.
|
I did some testing last night and verified that the max spell ID that can be used right now is 9999. I tested this by importing an EQlive spell file (over 20k spells) into excel and copied the holy armor spell and pasted it over all spell columns accept for the spell ID. So essentially all spells were now Holy Armor. Copied that same file over to my eqemu and everquest directories and loaded the server and logged in. I then #cast spells and clicked off the buff each time after the cast. I found that 9999 would cast and put the buff up, but 10000 would cast and not show the buff.
So, if I am reading what Secrets wrote correctly, the 10000 limit is built into the client and NOT the emu? I tried searching through the emu code, but I don't understand code well enough to be sure that there isn't any issues with the server that might cause spells above 10000 to not show up properly when cast. They do actually cast, but no buff icons show up and the buff message doesn't appear. Oddly, the worn off message still does appear...
If I am using the live version of the spells_us.txt file on both client and server, I can do a scribe spell and it seems to load all up to 9999. Anything above that shows up as "unknown spell" in the spell book and you are unable to scribe it or see any benefits from it.
I am no coder, but I read through alot of the emu code trying to understand how it loads the spells file. First I see this:
files.h
Code:
#ifndef SPELLS_FILE
#define SPELLS_FILE BASEDIR "spells_us.txt"
#endif
Which I think means that spells_us.txt can now be referenced as SPELLS_FILE now.
Then I look for that:
net.cpp
Code:
#ifdef NEW_LoadSPDat
// For NewLoadSPDat function
const SPDat_Spell_Struct* spells;
SPDat_Spell_Struct* spells_delete;
sint32 GetMaxSpellID();
void LoadSPDat();
bool FileLoadSPDat(SPDat_Spell_Struct* sp, sint32 iMaxSpellID);
sint32 SPDAT_RECORDS = -1;
#else
#define SPDat_Location "spdat.eff"
SPDat_Spell_Struct spells[SPDAT_RECORDS];
void LoadSPDat(SPDat_Spell_Struct** SpellsPointer = 0);
#ifdef NEW_LoadSPDat
sint32 GetMaxSpellID() {
int tempid=0, oldid=-1;
char spell_line_start[2048];
char* spell_line = spell_line_start;
char token[64]="";
char seps[] = "^";
const char *spells_file=ZoneConfig::get()->SpellsFile.c_str();
I don't really understand this part at all lol.
Another thing I checked out is:
spells.h
Code:
struct MMFSpells_Struct {
uint32 SPDAT_RECORDS; // maxspellid + 1, size of array
SPDat_Spell_Struct spells[0];
};
I see the commet that says "maxspellid + 1, size of array", but I don't understand where the array is built or where maxspellid or even SPDAT_RECORDS are defined. If you look at the items.h, it is really clear how that array is built:
items.h
Code:
#define MMF_EQMAX_ITEMS 100000
struct MMFItems_Struct {
uint32 MaxItemID;
uint32 NextFreeIndex;
uint32 ItemCount;
uint32 ItemIndex[MMF_EQMAX_ITEMS+1];
Item_Struct Items[0];
};
The SPDAT code has me confused too. I see where it used to be defined by a number, but now I don't see any define for it. I am not sure why "#define SPELL_UNKNOWN 0xFFFF" is only 16 bit when SPELLBOOK_UNKNOWN are 32 bit. I also don't know why it mentions "#define SPDAT_RECORDS 3602" as an else below.
spdat.h
Code:
#define SPELL_UNKNOWN 0xFFFF
#define SPELLBOOK_UNKNOWN 0xFFFFFFFF //player profile spells are 32 bit
//#define SPDAT_SIZE 1824000
/*
solar: look at your spells_en.txt and find the id of the last spell.
this number has to be 1 more than that. if it's higher, your zone will
NOT start up. gonna autodetect this later..
*/
#define NEW_LoadSPDat
#define EFFECT_COUNT 12
enum SpellAffectIndex {
SAI_Calm = 12, // Lull and Alliance Spells
SAI_Dispell_Sight = 14, // Dispells and Spells like Bind Sight
SAI_Memory_Blur = 27,
SAI_Calm_Song = 43 // Lull and Alliance Songs
};
#ifdef NEW_LoadSPDat
extern const SPDat_Spell_Struct* spells;
extern sint32 SPDAT_RECORDS;
#else
#define SPDAT_RECORDS 3602
Noting spells code from the emusharemem.h file here as well for reference.
emusharemem.h
Code:
////////////
// Spells //
////////////
typedef bool(*CALLBACK_FileLoadSPDat)(void*, sint32);
typedef bool(*DLLFUNC_DLLLoadSPDat)(const CALLBACK_FileLoadSPDat, const void**, sint32*, int32);
struct SpellsDLLFunc_Struct {
DLLFUNC_DLLLoadSPDat DLLLoadSPDat;
};
I noticed that the spells is the only one that doesn't have a clear function in the emusharemem.cpp code below. I don't know if that is anything suspicious or if that is normal.
Emusharemem.cpp
Code:
if (Loaded()) {
Items.GetItem = (DLLFUNC_GetItem) GetSym("GetItem");
Items.IterateItems = (DLLFUNC_IterateItems) GetSym("IterateItems");
Items.cbAddItem = (DLLFUNC_AddItem) GetSym("AddItem");
Items.DLLLoadItems = (DLLFUNC_DLLLoadItems) GetSym("DLLLoadItems");
Doors.GetDoor = (DLLFUNC_GetDoor) GetSym("GetDoor");
Doors.cbAddDoor = (DLLFUNC_AddDoor) GetSym("AddDoor");
Doors.DLLLoadDoors = (DLLFUNC_DLLLoadDoors) GetSym("DLLLoadDoors");
Spells.DLLLoadSPDat = (DLLFUNC_DLLLoadSPDat) GetSym("DLLLoadSPDat");
NPCFactionList.DLLLoadNPCFactionLists = (DLLFUNC_DLLLoadNPCFactionLists) GetSym("DLLLoadNPCFactionLists");
NPCFactionList.GetNPCFactionList = (DLLFUNC_GetNPCFactionList) GetSym("GetNPCFactionList");
NPCFactionList.cbAddNPCFactionList = (DLLFUNC_AddNPCFactionList) GetSym("AddNPCFactionList");
NPCFactionList.cbSetFaction = (DLLFUNC_SetFaction) GetSym("SetNPCFaction");
Loot.DLLLoadLoot = (DLLFUNC_DLLLoadLoot) GetSym("DLLLoadLoot");
Loot.cbAddLootTable = (DLLFUNC_AddLootTable) GetSym("AddLootTable");
Loot.cbAddLootDrop = (DLLFUNC_AddLootDrop) GetSym("AddLootDrop");
Loot.GetLootTable = (DLLFUNC_GetLootTable) GetSym("GetLootTable");
Loot.GetLootDrop = (DLLFUNC_GetLootDrop) GetSym("GetLootDrop");
Opcodes.GetEQOpcode = (DLLFUNC_GetEQOpcode) GetSym("GetEQOpcode");
Opcodes.GetEmuOpcode = (DLLFUNC_GetEmuOpcode) GetSym("GetEmuOpcode");
Opcodes.SetOpcodePair = (DLLFUNC_SetOpcodePair) GetSym("SetOpcodePair");
Opcodes.DLLLoadOpcodes = (DLLFUNC_DLLLoadOpcodes) GetSym("DLLLoadOpcodes");
Opcodes.ClearEQOpcodes = (DLLFUNC_ClearEQOpcodes) GetSym("ClearEQOpcodes");
SkillCaps.LoadSkillCaps = (DLLFUNC_DLLLoadSkillCaps) GetSym("LoadSkillCaps");
SkillCaps.GetSkillCap = (DLLFUNC_GetSkillCap) GetSym("GetSkillCap");
SkillCaps.SetSkillCap = (DLLFUNC_SetSkillCap) GetSym("SetSkillCap");
SkillCaps.ClearSkillCaps = (DLLFUNC_ClearSkillCaps) GetSym("ClearSkillCaps");
SkillCaps.GetTrainLevel = (DLLFUNC_GetTrainLevel) GetSym("GetTrainLevel");
void LoadEMuShareMemDLL::ClearFunc() {
Items.GetItem = 0;
Items.IterateItems = 0;
Items.cbAddItem = 0;
Items.DLLLoadItems = 0;
Doors.GetDoor = 0;
Doors.cbAddDoor = 0;
Doors.DLLLoadDoors = 0;
NPCFactionList.DLLLoadNPCFactionLists = 0;
NPCFactionList.GetNPCFactionList = 0;
NPCFactionList.cbAddNPCFactionList = 0;
NPCFactionList.cbSetFaction = 0;
Loot.DLLLoadLoot = 0;
Loot.cbAddLootTable = 0;
Loot.cbAddLootDrop = 0;
Loot.GetLootTable = 0;
Loot.GetLootDrop = 0;
Opcodes.GetEQOpcode = NULL;
Opcodes.GetEmuOpcode = NULL;
Opcodes.SetOpcodePair = NULL;
Opcodes.DLLLoadOpcodes = NULL;
Opcodes.ClearEQOpcodes = NULL;
SkillCaps.LoadSkillCaps = NULL;
SkillCaps.GetSkillCap = NULL;
SkillCaps.SetSkillCap = NULL;
SkillCaps.ClearSkillCaps = NULL;
SkillCaps.GetTrainLevel = NULL;
loaded = false;
}
I am curious about the SPELL_UNKNOWN and maxlevel referenced below. I would think current character level is the only thing that should factor into any spell files. I assume by maxlevel, it is referencing the rule set on the server? My server is set to level 70 max level in the rules, but I have a quest that lets players get to 75 and their spells disappear from their spell book when they zone. I don't see any reason to reference anything other than current level. Otherwise, is there a way I could change the max level references to refer to 75 instead of 70 which is what my rule is set for?
The SPELL_UNKNOWN stands out because the spells show up as "unknown spell" in the spell book and that is what is in the message when you #scribespells 75 for spell IDs over 9999.
client_packet.cpp
Code:
//Validity check for memorized
if(Admin() < minStatusToHaveInvalidSpells) {
for (uint32 mem = 0; mem < MAX_PP_MEMSPELL; mem++)
{
if (m_pp.mem_spells[mem] < 1 || m_pp.mem_spells[mem] >= (unsigned int)SPDAT_RECORDS || spells[m_pp.mem_spells[mem]].classes[GetClass()-1] < 1 || spells[m_pp.mem_spells[mem]].classes[GetClass()-1] > GetLevel())
m_pp.mem_spells[mem] = SPELLBOOK_UNKNOWN;
}
int maxlvl = RuleI(Character, MaxLevel);
for (uint32 bk = 0; bk < MAX_PP_SPELLBOOK; bk++)
{
if (m_pp.spell_book[bk] < 1
|| m_pp.spell_book[bk] >= (unsigned int)SPDAT_RECORDS
|| spells[m_pp.spell_book[bk]].classes[GetClass()-1] < 1
|| spells[m_pp.spell_book[bk]].classes[GetClass()-1] > maxlvl)
m_pp.spell_book[bk] = SPELLBOOK_UNKNOWN;
//I believe these effects are stripped off because if they
//are not, they result in permanent effects on the player
for (uint32 j1=0; j1 < BUFF_COUNT; j1++) {
if (buffs[j1].spellid <= (int32)SPDAT_RECORDS) {
for (uint32 x1=0; x1 < EFFECT_COUNT; x1++) {
switch (spells[buffs[j1].spellid].effectid[x1]) {
case SE_Charm:
//case SE_Rune:
buffs[j1].spellid = SPELL_UNKNOWN;
m_pp.buffs[j1].spellid = SPELLBOOK_UNKNOWN;
m_pp.buffs[j1].slotid = 0;
m_pp.buffs[j1].level = 0;
m_pp.buffs[j1].duration = 0;
m_pp.buffs[j1].effect = 0;
x1 = EFFECT_COUNT;
break;
case SE_Illusion:
if(m_pp.buffs[j1].persistant_buff != 1){ //anything other than 1=non persistant
buffs[j1].spellid = SPELL_UNKNOWN;
m_pp.buffs[j1].spellid = SPELLBOOK_UNKNOWN;
m_pp.buffs[j1].slotid = 0;
m_pp.buffs[j1].level = 0;
m_pp.buffs[j1].duration = 0;
m_pp.buffs[j1].effect = 0;
x1 = EFFECT_COUNT;
I am sure there is mostly irrelevant code in this post, but I just added anything that stood out to me. I am trying to confirm 100% wether the 9999 max spell id limit is determined by the Titanium client or the eqemu server. I think it would be a HUGE help to the community to find a way to allow more like 20k or 25k spell IDs.
If it is the client that limits this, is there any possible chance that the files could be changed or maybe update 1 or 2 from live that will up this? I highly doubt it, but worth asking lol.