Browse Source

- Spells now has class lists to reduce the time to assign new spells by level
- Fix #467 Tradeskill spells are now sent upon levelling (if not already assigned by starting spells)
- Wards now limit themselves to one "tic"/"trigger" per hit.
- New LUA Functions:
DeleteSpellBook(Player, type)
type = bit operand combination of:
DELETE_TRADESKILLS = 1,
DELETE_SPELLS = 2,
DELETE_COMBAT_ART = 4,
DELETE_ABILITY = 8,
DELETE_NOT_SHOWN = 16
Eg. tradeskills and spells = (1+2) = 3

SendNewAdventureSpells(Player) - sends adventure spells by their level
SendNewTradeskillSpells(Player) - sends tradeskill spells by their level
RemoveSpellBookEntry(Player, spell_id)

Emagi 1 year ago
parent
commit
417bea4214

+ 1 - 1
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -11860,7 +11860,7 @@ void Commands::Command_CurePlayer(Client* client, Seperator* sep)
 						entry = client->GetPlayer()->GetSpellBookSpell(rule_manager.GetGlobalRule(R_Spells, CureSpellID)->GetInt32()); // cure
 					}
 					
-					if(entry) {
+					if(entry && entry->spell_id) {
 						Spell* spell = master_spell_list.GetSpell(entry->spell_id, entry->tier);
 						target->GetZone()->ProcessSpell(spell, (Entity*)client->GetPlayer(), target, true, false);
 						successful_spell = true;

+ 9 - 1
EQ2/source/WorldServer/Entity.cpp

@@ -2025,6 +2025,9 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
 	while (m_wardList.size() > 0 && damage > 0) {
 		// Get the ward with the lowest base damage
 		for (itr = m_wardList.begin(); itr != m_wardList.end(); itr++) {
+			if(itr->second->RoundTriggered)
+				continue;
+			
 			if (!ward || itr->second->BaseDamage < ward->BaseDamage) {
 				if ((itr->second->AbsorbAllDamage || itr->second->DamageLeft > 0) &&
 					(itr->second->WardType == WARD_TYPE_ALL ||
@@ -2130,7 +2133,8 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
 
 		bool shouldRemoveSpell = false;
 		ward->HitCount++; // increment hit count
-
+		ward->RoundTriggered = true;
+		
 		if (ward->MaxHitCount && spell->num_triggers)
 		{
 			spell->num_triggers--;
@@ -2149,6 +2153,10 @@ int32 Entity::CheckWards(Entity* attacker, int32 damage, int8 damage_type) {
 		// Reset ward pointer
 		ward = 0;
 	}
+	
+	for (itr = m_wardList.begin(); itr != m_wardList.end(); itr++) {
+		itr->second->RoundTriggered = false;
+	}
 
 	return damage;
 }

+ 2 - 0
EQ2/source/WorldServer/Entity.h

@@ -1168,6 +1168,8 @@ struct WardInfo {
 	int32		MaxHitCount;
 
 	bool		AbsorbAllDamage; // damage is always absorbed, usually spells based on hits, when we pass damage in AddWard as 0 this will be set to true
+	
+	bool		RoundTriggered;
 };
 
 #define WARD_TYPE_ALL 0

+ 63 - 3
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -4771,16 +4771,75 @@ int EQ2Emu_lua_AddSpellBookEntry(lua_State* state) {
 		}
 	}
 	return 0;
+}
 
+int EQ2Emu_lua_DeleteSpellBook(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	int8 type_selection = lua_interface->GetInt8Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
+	if (player && player->IsPlayer()) {
+		Client* client = player->GetClient();
+		if (client) {
+			((Player*)player)->DeleteSpellBook(type_selection);
+			EQ2Packet* outapp = ((Player*)player)->GetSpellBookUpdatePacket(client->GetVersion());
+			if (outapp)
+				client->QueuePacket(outapp);
+		}
+	}
+	return 0;
+}
 
+int EQ2Emu_lua_SendNewAdventureSpells(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
+	if (player && player->IsPlayer()) {
+		Client* client = player->GetClient();
+		if (client) {
+			client->SendNewAdventureSpells();
+		}
+	}
+	return 0;
+}
 
 
+int EQ2Emu_lua_SendNewTradeskillSpells(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	lua_interface->ResetFunctionStack(state);
+	if (player && player->IsPlayer()) {
+		Client* client = player->GetClient();
+		if (client) {
+			client->SendNewTradeskillSpells();
+		}
+	}
+	return 0;
+}
 
-
-
-
+int EQ2Emu_lua_RemoveSpellBookEntry(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+	Spawn* player = lua_interface->GetSpawn(state);
+	int32 spellid = lua_interface->GetInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
+	if (player && player->IsPlayer()) {
+		SpellBookEntry* sbe = ((Player*)player)->GetSpellBookSpell(spellid);
+		Client* client = player->GetClient();
+		if (sbe && client) {
+			((Player*)player)->RemoveSpellBookEntry(spellid);
+			EQ2Packet* outapp = ((Player*)player)->GetSpellBookUpdatePacket(client->GetVersion());
+			if (outapp)
+				client->QueuePacket(outapp);
+		}
+	}
+	return 0;
 }
 
+
 int EQ2Emu_lua_HasFreeSlot(lua_State* state) {
 	if (!lua_interface)
 		return 0;
@@ -6256,6 +6315,7 @@ int EQ2Emu_lua_AddWard(lua_State* state) {
 			spell->had_triggers = true;
 			spell->cancel_after_all_triggers = false;
 			ward->MaxHitCount = maxHitCount;
+			ward->RoundTriggered = false;
 
 			if (wardType == WARD_TYPE_MAGICAL)
 				ward->DamageType = damageTypes;

+ 4 - 0
EQ2/source/WorldServer/LuaFunctions.h

@@ -226,6 +226,10 @@ int EQ2Emu_lua_SetServerControlFlag(lua_State* state);
 int EQ2Emu_lua_ToggleTracking(lua_State* state);
 int EQ2Emu_lua_AddPrimaryEntityCommand(lua_State* state);
 int EQ2Emu_lua_AddSpellBookEntry(lua_State* state);
+int EQ2Emu_lua_DeleteSpellBook(lua_State* state);
+int EQ2Emu_lua_RemoveSpellBookEntry(lua_State* state);
+int EQ2Emu_lua_SendNewAdventureSpells(lua_State* state);
+int EQ2Emu_lua_SendNewTradeskillSpells(lua_State* state);
 int EQ2Emu_lua_HasSpell(lua_State* state);
 int EQ2Emu_lua_Attack(lua_State* state);
 int EQ2Emu_lua_ApplySpellVisual(lua_State* state);

+ 4 - 0
EQ2/source/WorldServer/LuaInterface.cpp

@@ -1104,6 +1104,10 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "ToggleTracking", EQ2Emu_lua_ToggleTracking);
 	lua_register(state, "AddPrimaryEntityCommand", EQ2Emu_lua_AddPrimaryEntityCommand);
 	lua_register(state, "AddSpellBookEntry", EQ2Emu_lua_AddSpellBookEntry);
+	lua_register(state, "DeleteSpellBook", EQ2Emu_lua_DeleteSpellBook);
+	lua_register(state, "RemoveSpellBookEntry", EQ2Emu_lua_RemoveSpellBookEntry);
+	lua_register(state, "SendNewAdventureSpells", EQ2Emu_lua_SendNewAdventureSpells);
+	lua_register(state, "SendNewTradeskillSpells", EQ2Emu_lua_SendNewTradeskillSpells);
 	lua_register(state, "HasSpell", EQ2Emu_lua_HasSpell);
 	lua_register(state, "Interrupt", EQ2Emu_lua_Interrupt);
 	lua_register(state, "Stealth", EQ2Emu_lua_Stealth);

+ 34 - 1
EQ2/source/WorldServer/Player.cpp

@@ -2137,6 +2137,40 @@ void Player::AddSpellBookEntry(int32 spell_id, int8 tier, sint32 slot, int32 typ
 		AddPassiveSpell(spell_id, tier);
 }
 
+void Player::DeleteSpellBook(int8 type_selection){
+	MSpellsBook.lock();
+	vector<SpellBookEntry*>::iterator itr;
+	SpellBookEntry* spell = 0;
+	for(itr = spells.begin(); itr != spells.end();){
+		spell = *itr;
+		if((type_selection & DELETE_TRADESKILLS) == 0 && spell->type == SPELL_BOOK_TYPE_TRADESKILL) {
+			itr++;
+			continue;
+		}
+		else if((type_selection & DELETE_SPELLS) == 0 && spell->type == SPELL_BOOK_TYPE_SPELL) {
+			itr++;
+			continue;
+		}
+		else if((type_selection & DELETE_COMBAT_ART) == 0 && spell->type == SPELL_BOOK_TYPE_COMBAT_ART) {
+			itr++;
+			continue;
+		}
+		else if((type_selection & DELETE_ABILITY) == 0 && spell->type == SPELL_BOOK_TYPE_ABILITY) {
+			itr++;
+			continue;
+		}
+		else if((type_selection & DELETE_NOT_SHOWN) == 0 && spell->type == SPELL_BOOK_TYPE_NOT_SHOWN) {
+			itr++;
+			continue;
+		}
+		database.DeleteCharacterSpell(GetCharacterID(), spell->spell_id);
+		if (spell->type == SPELL_BOOK_TYPE_NOT_SHOWN)
+			RemovePassive(spell->spell_id, spell->tier, true);
+		itr = spells.erase(itr);
+	}
+	MSpellsBook.unlock();
+}
+
 void Player::RemoveSpellBookEntry(int32 spell_id, bool remove_passives_from_list){
 	MSpellsBook.lock();
 	vector<SpellBookEntry*>::iterator itr;
@@ -2758,7 +2792,6 @@ EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) {
 		int32 total_bytes = packet2->GetTotalPacketSize();
 		safe_delete(packet2);
 		packet->setArrayLengthByName("spell_count", count);
-		
 		if (count > 0) {
 			if (count > spell_count) {
 				uchar* tmp = 0;

+ 9 - 0
EQ2/source/WorldServer/Player.h

@@ -844,6 +844,15 @@ public:
 			pending_item_rewards.clear();
 		}
 	}
+	
+	enum DELETE_BOOK_TYPE {
+		DELETE_TRADESKILLS = 1,
+		DELETE_SPELLS = 2,
+		DELETE_COMBAT_ART = 4,
+		DELETE_ABILITY = 8,
+		DELETE_NOT_SHOWN = 16
+	};
+	void DeleteSpellBook(int8 type_selection = 0);
 	void RemoveSpellBookEntry(int32 spell_id, bool remove_passives_from_list = true);
 	void ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 maxlvl_only, int32 book_type);
 	void GetSpellBookSlotSort(int32 pattern, int32* i, int8* page_book_count, int32* last_start_point);

+ 30 - 4
EQ2/source/WorldServer/Spells.cpp

@@ -2247,11 +2247,28 @@ void MasterSpellList::DestroySpells(){
 		}
 	}
 	spell_list.clear();
+	for(int i=0;i<MAX_CLASSES;i++) {
+		class_spell_list[i].clear();
+	}
 	MMasterSpellList.unlock();
 }
 void MasterSpellList::AddSpell(int32 id, int8 tier, Spell* spell){
 	MMasterSpellList.lock();
 	spell_list[id][tier] = spell;
+	
+	vector<LevelArray*>* levels = spell->GetSpellLevels();
+	LevelArray* level = 0;
+	vector<LevelArray*>::iterator level_itr;
+	for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){
+		level = *level_itr;
+		if(level->adventure_class && level->adventure_class < MAX_CLASSES){
+			class_spell_list[level->adventure_class][id][tier] = spell;
+		}
+		if(level->tradeskill_class && level->tradeskill_class < MAX_CLASSES) {
+			class_spell_list[level->tradeskill_class][id][tier] = spell;
+		}
+	}
+	
 	spell_name_map[spell->GetName()] = spell;
 	spell_soecrc_map[spell->GetSpellData()->soe_spell_crc] = spell;
 
@@ -2360,6 +2377,10 @@ EQ2Packet* MasterSpellList::GetSpecialSpellPacket(int32 id, int8 tier, Client* c
 
 vector<Spell*>* MasterSpellList::GetSpellListByAdventureClass(int8 class_id, int16 max_level, int8 max_tier){
 	vector<Spell*>* ret = new vector<Spell*>;
+	if(class_id >= MAX_CLASSES) {
+		return ret;
+	}
+	
 	Spell* spell = 0;
 	vector<LevelArray*>* levels = 0;
 	LevelArray* level = 0;
@@ -2368,10 +2389,11 @@ vector<Spell*>* MasterSpellList::GetSpellListByAdventureClass(int8 class_id, int
 	map<int32, map<int32, Spell*> >::iterator iter;
 	map<int32, Spell*>::iterator iter2;
 	max_level *= 10; //convert to client level format, which is 10 times higher
-	for(iter = spell_list.begin();iter != spell_list.end(); iter++){
+	for(iter = class_spell_list[class_id].begin();iter != class_spell_list[class_id].end(); iter++){
 		for(iter2 = iter->second.begin();iter2 != iter->second.end(); iter2++){
 			spell = iter2->second;
-			if(iter2->first <= max_tier && spell && spell->GetSpellData()->given_by_type != GivenByType::GivenBy_SpellScroll){
+			if(iter2->first <= max_tier && spell && spell->GetSpellData()->given_by_type != GivenByType::GivenBy_SpellScroll && 
+				spell->GetSpellData()->given_by_type != GivenByType::GivenBy_TradeskillClass){
 				levels = spell->GetSpellLevels();
 				for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){
 					level = *level_itr;
@@ -2389,6 +2411,10 @@ vector<Spell*>* MasterSpellList::GetSpellListByAdventureClass(int8 class_id, int
 
 vector<Spell*>* MasterSpellList::GetSpellListByTradeskillClass(int8 class_id, int16 max_level, int8 max_tier){
 	vector<Spell*>* ret = new vector<Spell*>;
+	if(class_id >= MAX_CLASSES) {
+		return ret;
+	}
+	
 	Spell* spell = 0;
 	vector<LevelArray*>* levels = 0;
 	LevelArray* level = 0;
@@ -2396,10 +2422,10 @@ vector<Spell*>* MasterSpellList::GetSpellListByTradeskillClass(int8 class_id, in
 	MMasterSpellList.lock();
 	map<int32, map<int32, Spell*> >::iterator iter;
 	map<int32, Spell*>::iterator iter2;
-	for(iter = spell_list.begin();iter != spell_list.end(); iter++){
+	for(iter = class_spell_list[class_id].begin();iter != class_spell_list[class_id].end(); iter++){
 		for(iter2 = iter->second.begin();iter2 != iter->second.end(); iter2++){
 			spell = iter2->second;
-			if(iter2->first <= max_tier && spell && spell->GetSpellData()->given_by_type != GivenByType::GivenBy_SpellScroll){
+			if(iter2->first <= max_tier && spell && spell->GetSpellData()->given_by_type == GivenByType::GivenBy_TradeskillClass){
 				levels = spell->GetSpellLevels();
 				for(level_itr = levels->begin(); level_itr != levels->end(); level_itr++){
 					level = *level_itr;

+ 2 - 0
EQ2/source/WorldServer/Spells.h

@@ -27,6 +27,7 @@
 #include "../common/EQPacket.h"
 #include "../common/MiscFunctions.h"
 #include "client.h"
+#include "classes.h"
 #include "../common/Mutex.h"
 #include "AltAdvancement/AltAdvancement.h"
 
@@ -394,6 +395,7 @@ public:
 	void DestroySpells();
 	map<string, Spell*> spell_name_map;
 	map<int32, map<int32, Spell* > > spell_list;
+	map<int32, map<int32, Spell* > > class_spell_list[MAX_CLASSES];
 	map<int32, Spell*> spell_soecrc_map;
 	Spell* GetSpell(int32 id, int8 tier);
 	vector<Spell*>* GetSpellListByAdventureClass(int8 class_id, int16 max_level, int8 max_tier);

+ 1 - 0
EQ2/source/WorldServer/classes.h

@@ -96,6 +96,7 @@ using namespace std;
 	//52 - jeweler
 	//53 - sage
 	//54 - alch
+#define MAX_CLASSES		58
 
 class Classes {
 public:

+ 54 - 25
EQ2/source/WorldServer/client.cpp

@@ -4673,10 +4673,7 @@ void Client::ChangeLevel(int16 old_level, int16 new_level) {
 	if (!player->get_character_flag(CF_ENABLE_CHANGE_LASTNAME) && new_level >= rule_manager.GetGlobalRule(R_Player, MinLastNameLevel)->GetInt8())
 		player->set_character_flag(CF_ENABLE_CHANGE_LASTNAME);
 
-	SendNewSpells(player->GetAdventureClass());
-	SendNewSpells(classes.GetBaseClass(player->GetAdventureClass()));
-	SendNewSpells(classes.GetSecondaryBaseClass(player->GetAdventureClass()));
-
+	SendNewAdventureSpells();
 
 	GetPlayer()->ChangePrimaryWeapon();
 	GetPlayer()->ChangeSecondaryWeapon();
@@ -4835,10 +4832,10 @@ void Client::ChangeTSLevel(int16 old_level, int16 new_level) {
 		QueuePacket(level_update->serialize());
 		safe_delete(level_update);
 	}
-	// Need to make tradeskill versions of the following
-	//SendNewSpells(player->GetAdventureClass());
-	//SendNewSpells(classes.GetBaseClass(player->GetAdventureClass()));
-	//SendNewSpells(classes.GetSecondaryBaseClass(player->GetAdventureClass()));
+	
+	// provide new spells upon levelling
+	SendNewTradeskillSpells();
+	
 	PacketStruct* command_packet = configReader.getStruct("WS_CannedEmote", GetVersion());
 	if (command_packet) {
 		command_packet->setDataByName("spawn_id", GetPlayer()->GetIDWithPlayerSpawn(GetPlayer()));
@@ -9094,26 +9091,38 @@ void Client::ProcessTeleportLocation(EQApplicationPacket* app) {
 void Client::SendNewSpells(int8 class_id) {
 	if (class_id > 0) {
 		vector<Spell*>* spells = master_spell_list.GetSpellListByAdventureClass(class_id, player->GetLevel(), 1);
-		vector<Spell*>::iterator itr;
-		Spell* spell = 0;
-		bool send_updates = false;
-		for (itr = spells->begin(); itr != spells->end(); itr++) {
-			spell = *itr;
-			if (spell && !player->HasSpell(spell->GetSpellID(), spell->GetSpellTier(), true) && spell->GetSpellData()->lua_script.length() > 0) {
-				send_updates = true;
-				SendSpellUpdate(spell);
-				player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), player->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true);
-				player->UnlockSpell(spell);
-			}
-		}
-		if (send_updates) {
-			EQ2Packet* outapp = player->GetSpellBookUpdatePacket(GetVersion());
-			if (outapp)
-				QueuePacket(outapp);
-		}
+		AddSendNewSpells(spells);
+		safe_delete(spells);
+	}
+
+}
+
+void Client::SendNewTSSpells(int8 class_id) {
+	if (class_id > 0) {
+		vector<Spell*>* spells = master_spell_list.GetSpellListByTradeskillClass(class_id, player->GetLevel(), 1);
+		AddSendNewSpells(spells);
 		safe_delete(spells);
 	}
+}
 
+void Client::AddSendNewSpells(vector<Spell*>* spells) {
+	Spell* spell = 0;
+	bool send_updates = false;
+	vector<Spell*>::iterator itr;
+	for (itr = spells->begin(); itr != spells->end(); itr++) {
+		spell = *itr;
+		if (spell && !player->HasSpell(spell->GetSpellID(), spell->GetSpellTier(), true) && spell->GetSpellData()->lua_script.length() > 0) {
+			send_updates = true;
+			SendSpellUpdate(spell);
+			player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), player->GetFreeSpellBookSlot(spell->GetSpellData()->spell_book_type), spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true);
+			player->UnlockSpell(spell);
+		}
+	}
+	if (send_updates) {
+		EQ2Packet* outapp = player->GetSpellBookUpdatePacket(GetVersion());
+		if (outapp)
+			QueuePacket(outapp);
+	}
 }
 
 void Client::SetItemSearch(vector<Item*>* items) {
@@ -11786,4 +11795,24 @@ bool Client::IsLinkdeadTimerEnabled() {
 	}
 	
 	return false;
+}
+
+void Client::SendNewAdventureSpells() {
+	SendNewSpells(player->GetAdventureClass());
+	int8 base_class = classes.GetBaseClass(player->GetAdventureClass());
+	int secondary_class = classes.GetSecondaryBaseClass(player->GetAdventureClass());
+	if(base_class != player->GetAdventureClass()) {
+		SendNewSpells(base_class);
+	}
+	if(secondary_class != player->GetAdventureClass() && secondary_class != base_class) {
+		SendNewSpells(secondary_class);
+	}
+}
+
+void Client::SendNewTradeskillSpells() {
+	SendNewTSSpells(player->GetTradeskillClass());
+	int8 secondary_class = classes.GetSecondaryTSBaseClass(player->GetTradeskillClass());
+	if(secondary_class != player->GetTradeskillClass()) { 
+		SendNewTSSpells(secondary_class);
+	}
 }

+ 7 - 1
EQ2/source/WorldServer/client.h

@@ -360,7 +360,8 @@ public:
 	void	SendChatRelationship(int8 type, const char* name);
 	void	SendFriendList();
 	void	SendIgnoreList();
-	void	SendNewSpells(int8 class_id);
+	void	SendNewAdventureSpells();
+	void	SendNewTradeskillSpells();
 	string	GetCoinMessage(int32 total_coins);
 	void	SetItemSearch(vector<Item*>* items);
 	vector<Item*>* GetSearchItems();
@@ -589,6 +590,11 @@ private:
 	void	GiveQuestReward(Quest* quest, bool has_displayed = false);
 	void	SetStepComplete(int32 quest_id, int32 step);
 	void	AddStepProgress(int32 quest_id, int32 step, int32 progress);
+	
+	void	SendNewSpells(int8 class_id);
+	void	SendNewTSSpells(int8 class_id);
+	void	AddSendNewSpells(vector<Spell*>* spells);
+	
 	map<int32, map<int32, int32> > quest_pending_updates;
 	vector<QueuedQuest*> quest_queue;
 	vector<QuestRewardData*> quest_pending_reward;