Fix #451 - basic pvp mitigation / mitigation integration

Fix #458 - fixed memory leaks in lua quest step location/zone loc functions and also languages memory cleanup

New Rules:
       RULE_INIT(R_PVP, PVPMitigationModByLevel, "25"); // gives a bonus to mitigation for PVP combat to offset the percentage level * mod (default 25)
       RULE_INIT(R_Combat, EffectiveMitigationCapLevel, "80"); // level multiplier for max effective cap, level * 80 (default)
       RULE_INIT(R_Combat, CalculatedMitigationCapLevel, "100"); // The cap to calculate your mitigation from is [level*100].
       RULE_INIT(R_Combat, MitigationLevelEffectivenessMax, "1.5"); // ratio victim level / attacker level for max effectiveness, when victim is higher level cap can reach 1.5
       RULE_INIT(R_Combat, MitigationLevelEffectivenessMin, ".5"); // ratio victim level / attacker level for min effectiveness
       RULE_INIT(R_Combat, MaxMitigationAllowed, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVE
       RULE_INIT(R_Combat, MaxMitigationAllowedPVP, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVP

InfoStruct now has two unsigned int16 values that offer mitigation percentage values in integer formats, eg 155 = 15.5% mitigation
mitigation_pve
mitigation_pvp

Updated formulas to use effective_level (mentor/actual level) vs just player level
* Dodge
* Block

Bug fix for crash in non existent quest being called for /modify quest advance
Bug fix for improperly trying to stack items that are not stackable (count of 0 items, stack count of 1)
Bug fix for inventory updates, typically with overflow slots and after deleting items from inventory (packet count needs to be updated with current size in PlayerItemList::serialize)

Collections/Rewards:
- Display is now one reward at a time
- Display of award now only allows the reward cash, status to be provided once
- Database persistence of unaccepted rewards cross-zone with two new tables
- Selectable collections now checks if either field provided in /accept_reward is a potential item id (this is likely due to a client versioning)
This commit is contained in:
Emagi 2022-08-06 18:22:29 -04:00
parent 3cd6dcc16a
commit 1068849ef8
20 changed files with 585 additions and 114 deletions

View file

@ -0,0 +1,17 @@
CREATE TABLE `character_quest_rewards` (
`char_id` int(10) unsigned NOT NULL default 0,
`quest_id` int(10) unsigned NOT NULL default 0,
`indexed` int(10) unsigned NOT NULL default 0,
`is_temporary` tinyint(3) unsigned NOT NULL default 0,
`is_collection` tinyint(3) unsigned NOT NULL default 0,
`has_displayed` tinyint(3) unsigned NOT NULL default 0,
`tmp_coin` bigint unsigned NOT NULL DEFAULT 0,
`tmp_status` int(10) unsigned NOT NULL default 0,
`description` text not null default ''
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `character_quest_temporary_rewards` (
`char_id` int(10) unsigned NOT NULL default 0,
`quest_id` int(10) unsigned NOT NULL default 0,
`item_id` int(10) unsigned NOT NULL default 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

View file

@ -912,6 +912,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
int8 hit_result = 0;
int16 blow_type = 0;
sint32 damage = 0;
sint32 damage_before_crit = 0;
bool crit = false;
if(low_damage > high_damage)
@ -949,6 +950,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
crit = true;
}
if(crit){
damage_before_crit = damage;
//Apply total crit multiplier with crit bonus
if(info_struct.get_crit_bonus() > 0)
damage *= (1.3 + (info_struct.get_crit_bonus() / 100));
@ -963,8 +965,21 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
}
}
// TODO: Mitigation equation from http://www.guildportal.com/Guild.aspx?GuildID=20881&TabID=189653&ForumID=95908&TopicID=9024250
if(type == DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE || type == DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG || type == DAMAGE_PACKET_TYPE_RANGE_DAMAGE) {
int16 effective_level_attacker = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
float mit_percentage = CalculateMitigation(type, damage_type, effective_level_attacker, (IsPlayer() && victim->IsPlayer()));
sint32 damage_to_reduce = (damage * mit_percentage);
if(damage_to_reduce > damage)
damage = 0;
else
damage -= damage_to_reduce;
// if we reduce damage back below crit level then its no longer a crit, but we don't go below base damage
if(type == DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG && damage <= damage_before_crit) {
damage = damage_before_crit;
type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE;
}
}
}
LogWrite(MISC__TODO, 3, "TODO", "Take players armor into account\nfile: %s, func: %s, line: %i)", __FILE__, __FUNCTION__, __LINE__);
@ -1057,6 +1072,73 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_
return crit;
}
float Entity::CalculateMitigation(int8 type, int8 damage_type, int16 effective_level_attacker, bool for_pvp) {
int16 effective_level_victim = GetInfoStruct()->get_effective_level() != 0 ? GetInfoStruct()->get_effective_level() : GetLevel();
if(effective_level_attacker < 1 && effective_level_victim)
effective_level_attacker = effective_level_victim;
else
effective_level_attacker = 1;
int32 effective_mit_cap = effective_level_victim * rule_manager.GetGlobalRule(R_Combat, EffectiveMitigationCapLevel)->GetInt32();
float max_mit = (float)GetInfoStruct()->get_max_mitigation();
if(max_mit == 0.0f)
max_mit = effective_level_victim * 100.0f;
int32 mit_to_use = 0;
switch(type) {
case DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE:
case DAMAGE_PACKET_TYPE_RANGE_DAMAGE:
mit_to_use = GetInfoStruct()->get_cur_mitigation();
break;
case DAMAGE_PACKET_TYPE_SIMPLE_CRIT_DMG:
// since critical mitigation is a percentage we will reverse the mit value so we can add skill from specific types of weapons
mit_to_use = (int32)((float)GetInfoStruct()->get_max_mitigation() * (float)(GetInfoStruct()->get_critical_mitigation()/100.0f));
break;
}
switch(damage_type) {
case DAMAGE_PACKET_DAMAGE_TYPE_SLASH:
mit_to_use += GetInfoStruct()->get_mitigation_skill1(); // slash
break;
case DAMAGE_PACKET_DAMAGE_TYPE_PIERCE:
mit_to_use += GetInfoStruct()->get_mitigation_skill2(); // pierce
break;
case DAMAGE_PACKET_DAMAGE_TYPE_CRUSH:
mit_to_use += GetInfoStruct()->get_mitigation_skill3(); // crush
break;
default:
// do nothing
break;
}
if(for_pvp) {
mit_to_use += effective_level_victim * rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetInt32();
}
if(mit_to_use > effective_mit_cap) {
mit_to_use = effective_mit_cap;
}
float level_diff = ((float)effective_level_victim / (float)effective_level_attacker);
if(level_diff > rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat()) {
level_diff = rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat();
}
else if(level_diff < rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMax)->GetFloat()) {
level_diff = rule_manager.GetGlobalRule(R_Combat, MitigationLevelEffectivenessMin)->GetFloat();
}
float mit_percentage = ((float)mit_to_use / max_mit) * level_diff;
if(!for_pvp && mit_percentage > rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowed)->GetFloat()) {
mit_percentage = rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowed)->GetFloat();
}
else if(for_pvp && mit_percentage > rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowedPVP)->GetFloat()) {
mit_percentage = rule_manager.GetGlobalRule(R_Combat, MaxMitigationAllowedPVP)->GetFloat();
}
return mit_percentage;
}
void Entity::AddHate(Entity* attacker, sint32 hate) {
if(!attacker || GetHP() <= 0 || attacker->GetHP() <= 0)
return;

View file

@ -4133,7 +4133,6 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
int32 selectable_item_id = 0;
//Quest *quest = 0;
Collection *collection = 0;
/* no idea what the first argument is for (faction maybe?)
if the reward has a selectable item reward, it's sent as the second argument
if neither of these are included in the reward, there is no sep
@ -4144,10 +4143,10 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
selectable_item_id = atoul(sep->arg[1]);
}
//if ((quest = player->GetPendingQuestReward()))
// client->AcceptQuestRewards(quest, selectable_item_id);
/* the below needs to go away eventually and be redone */
/* this logic here may seem unexpected, but the quest queue response for GetPendingQuestAcceptance is only populated if it is the current reward displayed to the client based on a quest
** Otherwise it will likely be a DoF client scenario (pending item rewards, selectable item rewards) which is specifying an item id
** lastly it will be a collection which also supplies an item id and you can only have one pending collection turn in at a time (they queue against Client::HandInCollections
*/
int32 item_id = 0;
if(sep && sep->arg[0][0] && sep->IsNumber(0))
item_id = atoul(sep->arg[0]);
@ -4157,12 +4156,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
break;
}
bool collectedItems = false;
if (collection = player->GetPendingCollectionReward())
{
client->AcceptCollectionRewards(collection, selectable_item_id);
collectedItems = true;
}
else if (client->GetPlayer()->HasPendingItemRewards()) {
if (client->GetPlayer()->HasPendingItemRewards()) {
vector<Item*> items = client->GetPlayer()->GetPendingItemRewards();
if (items.size() > 0) {
collectedItems = true;
@ -4170,6 +4164,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
client->GetPlayer()->AddItem(new Item(items[i]));
}
client->GetPlayer()->ClearPendingItemRewards();
client->GetPlayer()->SetActiveReward(false);
}
map<int32, Item*> selectable_item = client->GetPlayer()->GetPendingSelectableItemReward(item_id);
if (selectable_item.size() > 0) {
@ -4179,8 +4174,15 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
client->GetPlayer()->AddItem(new Item(itr->second));
client->GetPlayer()->ClearPendingSelectableItemRewards(itr->first);
}
client->GetPlayer()->SetActiveReward(false);
}
}
else if (collection = player->GetPendingCollectionReward())
{
client->AcceptCollectionRewards(collection, (selectable_item_id > 0) ? selectable_item_id : item_id);
collectedItems = true;
}
if (collectedItems) {
EQ2Packet* outapp = client->GetPlayer()->SendInventoryUpdate(client->GetVersion());
if (outapp)
@ -7729,6 +7731,10 @@ void Commands::Command_ModifyQuest(Client* client, Seperator* sep)
quest_id = atoul(sep->arg[1]);
Quest* quest = client->GetPlayer()->player_quests[quest_id];
if(!quest) {
client->Message(CHANNEL_COLOR_RED, "Quest not found!");
return;
}
if (sep && sep->arg[2] && sep->IsNumber(1))
{
step = atoul(sep->arg[2]);

View file

@ -249,6 +249,8 @@ void Entity::MapInfoStruct()
get_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::get_mitigation_skill1, &info_struct);
get_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::get_mitigation_skill2, &info_struct);
get_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::get_mitigation_skill3, &info_struct);
get_int16_funcs["mitigation_pve"] = l::bind(&InfoStruct::get_mitigation_pve, &info_struct);
get_int16_funcs["mitigation_pvp"] = l::bind(&InfoStruct::get_mitigation_pvp, &info_struct);
get_float_funcs["ability_modifier"] = l::bind(&InfoStruct::get_ability_modifier, &info_struct);
get_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::get_critical_mitigation, &info_struct);
get_float_funcs["block_chance"] = l::bind(&InfoStruct::get_block_chance, &info_struct);
@ -427,6 +429,8 @@ void Entity::MapInfoStruct()
set_int16_funcs["mitigation_skill1"] = l::bind(&InfoStruct::set_mitigation_skill1, &info_struct, l::_1);
set_int16_funcs["mitigation_skill2"] = l::bind(&InfoStruct::set_mitigation_skill2, &info_struct, l::_1);
set_int16_funcs["mitigation_skill3"] = l::bind(&InfoStruct::set_mitigation_skill3, &info_struct, l::_1);
set_int16_funcs["mitigation_pve"] = l::bind(&InfoStruct::set_mitigation_pve, &info_struct, l::_1);
set_int16_funcs["mitigation_pvp"] = l::bind(&InfoStruct::set_mitigation_pvp, &info_struct, l::_1);
set_float_funcs["ability_modifier"] = l::bind(&InfoStruct::set_ability_modifier, &info_struct, l::_1);
set_float_funcs["critical_mitigation"] = l::bind(&InfoStruct::set_critical_mitigation, &info_struct, l::_1);
set_float_funcs["block_chance"] = l::bind(&InfoStruct::set_block_chance, &info_struct, l::_1);
@ -1289,6 +1293,15 @@ void Entity::CalculateBonuses(){
CalculateSpellBonuses(values);
info->set_cur_mitigation(info->get_mitigation_base());
int32 calc_mit_cap = effective_level * rule_manager.GetGlobalRule(R_Combat, CalculatedMitigationCapLevel)->GetInt32();
info->set_max_mitigation(calc_mit_cap);
int16 mit_percent = (int16)(CalculateMitigation() * 1000.0f);
info->set_mitigation_pve(mit_percent);
mit_percent = (int16)(CalculateMitigation(DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE,0,0,true) * 1000.0f);
info->set_mitigation_pvp(mit_percent);
info->add_sta((float)values->sta);
info->add_str((float)values->str);
info->add_agi((float)values->agi);
@ -1397,10 +1410,10 @@ void Entity::CalculateBonuses(){
else if (skill->short_name.data == "buckler")
baseBlock = 3.0f;
}
if(GetLevel() > mitigation)
block_pct = log10f((float)mitigation/((float)GetLevel()*10.0f));
if(effective_level > mitigation)
block_pct = log10f((float)mitigation/((float)effective_level*10.0f));
else
block_pct = log10f(((float)GetLevel()/(float)mitigation)) * log10f(GetLevel()) * 2.0f;
block_pct = log10f(((float)effective_level/(float)mitigation)) * log10f(effective_level) * 2.0f;
if(block_pct < 0.0f)
block_pct *= -1.0f;
@ -1441,7 +1454,7 @@ void Entity::CalculateBonuses(){
float dodge_actual = 0.0f;
if(full_pct_hit > 0.0f)
dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + (log10f(GetLevel() * GetAgi()) / 100.0f);
dodge_actual = dodge_pct * (full_pct_hit / 100.0f) + (log10f(effective_level * GetAgi()) / 100.0f);
info->set_avoidance_base(dodge_actual);

View file

@ -190,6 +190,8 @@ struct InfoStruct{
mitigation_skill1_ = 0;
mitigation_skill2_ = 0;
mitigation_skill3_ = 0;
mitigation_pve_ = 0;
mitigation_pvp_ = 0;
ability_modifier_ = 0;
critical_mitigation_ = 0;
block_chance_ = 0;
@ -370,6 +372,8 @@ struct InfoStruct{
mitigation_skill1_ = oldStruct->get_mitigation_skill1();
mitigation_skill2_ = oldStruct->get_mitigation_skill2();
mitigation_skill3_ = oldStruct->get_mitigation_skill3();
mitigation_pve_ = oldStruct->get_mitigation_pve();
mitigation_pvp_ = oldStruct->get_mitigation_pvp();
ability_modifier_ = oldStruct->get_ability_modifier();
critical_mitigation_ = oldStruct->get_critical_mitigation();
block_chance_ = oldStruct->get_block_chance();
@ -557,6 +561,9 @@ struct InfoStruct{
int16 get_mitigation_skill1() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill1_; }
int16 get_mitigation_skill2() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill2_; }
int16 get_mitigation_skill3() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_skill3_; }
int16 get_mitigation_pve() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_pve_; }
int16 get_mitigation_pvp() { std::lock_guard<std::mutex> lk(classMutex); return mitigation_pvp_; }
float get_ability_modifier() { std::lock_guard<std::mutex> lk(classMutex); return ability_modifier_; }
float get_critical_mitigation() { std::lock_guard<std::mutex> lk(classMutex); return critical_mitigation_; }
@ -792,6 +799,9 @@ struct InfoStruct{
void set_mitigation_skill1(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill1_ = value; }
void set_mitigation_skill2(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill2_ = value; }
void set_mitigation_skill3(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill3_ = value; }
void set_mitigation_pve(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_pve_ = value; }
void set_mitigation_pvp(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_pvp_ = value; }
void add_mitigation_skill1(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill1_ += value; }
void add_mitigation_skill2(int16 value) { std::lock_guard<std::mutex> lk(classMutex); mitigation_skill2_ += value; }
@ -1037,6 +1047,8 @@ private:
int16 mitigation_skill1_;
int16 mitigation_skill2_;
int16 mitigation_skill3_;
int16 mitigation_pve_;
int16 mitigation_pvp_;
float ability_modifier_;
float critical_mitigation_;
float block_chance_;
@ -1363,6 +1375,7 @@ public:
float GetDamageTypeResistPercentage(int8 damage_type);
Skill* GetSkillByWeaponType(int8 type, bool update);
bool DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_damage, int32 high_damage, const char* spell_name, int8 crit_mod = 0, bool is_tick = false, bool no_damage_calcs = false, bool ignore_attacker = false, LuaSpell* spell = 0);
float CalculateMitigation(int8 type = DAMAGE_PACKET_TYPE_SIMPLE_DAMAGE, int8 damage_type = 0, int16 attacker_level = 0, bool for_pvp = false);
void AddHate(Entity* attacker, sint32 hate);
bool CheckInterruptSpell(Entity* attacker);
bool CheckFizzleSpell(LuaSpell* spell);

View file

@ -3126,8 +3126,9 @@ bool PlayerItemList::GetFirstFreeSlot(sint32* bag_id, sint16* slot) {
}
Item* PlayerItemList::CanStack(Item* item, bool include_bank){
if(!item)
if(!item || item->stack_count < 2)
return 0;
Item* ret = 0;
map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
map<int16, Item*>::iterator slot_itr;
@ -3135,13 +3136,13 @@ Item* PlayerItemList::CanStack(Item* item, bool include_bank){
for(itr = items.begin(); itr != items.end(); itr++){
if(include_bank || (!include_bank && itr->first >= 0)){
for(slot_itr=itr->second[0].begin();slot_itr!=itr->second[0].end(); slot_itr++){
if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && ((slot_itr->second->details.count + item->details.count) <= slot_itr->second->stack_count)){
if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && (((slot_itr->second->details.count ? slot_itr->second->details.count : 1) + (item->details.count > 0 ? item->details.count : 1)) <= slot_itr->second->stack_count)){
ret = slot_itr->second;
break;
}
}
for(slot_itr=itr->second[1].begin();slot_itr!=itr->second[1].end(); slot_itr++){
if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && ((slot_itr->second->details.count + item->details.count) <= slot_itr->second->stack_count)){
if(slot_itr->second && slot_itr->second->details.item_id == item->details.item_id && (((slot_itr->second->details.count ? slot_itr->second->details.count : 1) + (item->details.count > 0 ? item->details.count : 1)) <= slot_itr->second->stack_count)){
ret = slot_itr->second;
break;
}
@ -3416,9 +3417,10 @@ EQ2Packet* PlayerItemList::serialize(Player* player, int16 version){
safe_delete_array(xor_packet);
xor_packet = new uchar[packet_size * size];
}
packet_count = size;
}
packet_count = size;
for(int16 i = 0; i < indexed_items.size(); i++){
item = indexed_items[i];
if (item && item->details.item_id > 0)
@ -3574,9 +3576,10 @@ void PlayerItemList::AddItemToPacket(PacketStruct* packet, Player* player, Item*
packet->setSubstructArrayDataByName("items", "menu_type", menu_data, 0, i);
if (overflow)
packet->setSubstructArrayDataByName("items", "index", 0xFFFF, 0, i);
else
else {
packet->setSubstructArrayDataByName("items", "index", i, 0, i);
item->details.index = i;
item->details.index = i;
}
packet->setSubstructArrayDataByName("items", "icon", item->details.icon, 0, i);
packet->setSubstructArrayDataByName("items", "slot_id", item->details.slot_id, 0, i);
if (client->GetVersion() <= 1208) {

View file

@ -41,7 +41,15 @@ MasterLanguagesList::~MasterLanguagesList(){
Clear();
}
// don't bother calling this beyond its deconstructor its not thread-safe
void MasterLanguagesList::Clear(){
list<Language*>::iterator itr;
Language* language = 0;
for(itr = languages_list.begin(); itr != languages_list.end(); itr++){
language = *itr;
safe_delete(language);
}
languages_list.clear();
}

View file

@ -4092,6 +4092,7 @@ int EQ2Emu_lua_AddQuestStepZoneLoc(lua_State* state) {
i += 4;
}
QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation);
safe_delete(locations); // gets duplicated into new table in QuestStep constructor
if (quest_step && icon > 0)
quest_step->SetIcon(icon);
if (quest->GetPlayer()) {
@ -4134,6 +4135,7 @@ int EQ2Emu_lua_AddQuestStepLocation(lua_State* state) {
i += 3;
}
QuestStep* quest_step = quest->AddQuestStep(step, QUEST_STEP_TYPE_LOCATION, description, 0, 1, taskgroup, locations, max_variation);
safe_delete(locations); // gets duplicated into new table in QuestStep constructor
if (quest_step && icon > 0)
quest_step->SetIcon(icon);
if (quest->GetPlayer()) {
@ -4407,9 +4409,7 @@ int EQ2Emu_lua_GiveQuestReward(lua_State* state) {
if (quest && spawn) {
if (spawn->IsPlayer()) {
Client* client = spawn->GetZone()->GetClientBySpawn(spawn);
if (client)
{
client->AddPendingQuestAcceptReward(quest);
if (client) {
client->AddPendingQuestReward(quest);
}
}
@ -6034,8 +6034,7 @@ int EQ2Emu_lua_GiveQuestItem(lua_State* state)
itemsAddedSuccessfully = false;
}
}
client->AddPendingQuestAcceptReward(quest);
client->DisplayQuestComplete(quest, true, description);
client->AddPendingQuestReward(quest, true, true, description); // queue for display
lua_interface->SetBooleanValue(state, itemsAddedSuccessfully);
return 1;

View file

@ -403,9 +403,9 @@ Mutex* LuaInterface::GetQuestMutex(Quest* quest) {
return ret;
}
void LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id) {
bool LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id, int32* returnValue) {
if(shutting_down)
return;
return false;
lua_State* state = 0;
if(quest){
LogWrite(LUA__DEBUG, 0, "LUA", "Quest: %s, function: %s", quest->GetName(), function);
@ -413,6 +413,7 @@ void LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn*
mutex->lock();
if(quest_states.count(quest->GetQuestID()) > 0)
state = quest_states[quest->GetQuestID()];
bool success = false; // if no state then we return false
if(state){
int8 arg_count = 3;
lua_getglobal(state, function);
@ -424,16 +425,14 @@ void LuaInterface::CallQuestFunction(Quest* quest, const char* function, Spawn*
SetInt32Value(state, step_id);
arg_count++;
}
if(lua_pcall(state, arg_count, 0, 0) != 0){
LogError("%s: Error processing quest function '%s': %s ", GetScriptName(state), function, lua_tostring(state, -1));
lua_pop(state, 1);
mutex->unlock();
return;
}
success = CallScriptInt32(state, arg_count, returnValue);
}
mutex->unlock();
LogWrite(LUA__DEBUG, 0, "LUA", "Done!");
return success;
}
return false;
}
Quest* LuaInterface::LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name) {

View file

@ -276,7 +276,7 @@ public:
void LogError(const char* error, ...);
void CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id = 0xFFFFFFFF);
bool CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id = 0xFFFFFFFF, int32* returnValue = 0);
void RemoveDebugClients(Client* client);
void UpdateDebugClients(Client* client);
void ProcessErrorMessage(const char* message);

View file

@ -124,6 +124,7 @@ Player::Player(){
reset_mentorship = false;
all_spells_locked = false;
current_language_id = 0;
active_reward = false;
}
Player::~Player(){
SetSaveSpellEffects(true);
@ -703,8 +704,9 @@ EQ2Packet* PlayerInfo::serialize(int16 version, int16 modifyPos, int32 modifyVal
packet->setDataByName("stat_bonus_damage", 95); //stat_bonus_damage
packet->setDataByName("mitigation_cur", info_struct->get_cur_mitigation());// confirmed DoV
packet->setDataByName("mitigation_base", info_struct->get_mitigation_base());// confirmed DoV
packet->setDataByName("mitigation_pct_pve", 392); // % calculation Mitigation % vs PvE 392 = 39.2%// confirmed DoV
packet->setDataByName("mitigation_pct_pvp", 559); // % calculation Mitigation % vs PvP 559 = 55.9%// confirmed DoV
packet->setDataByName("mitigation_pct_pve", info_struct->get_mitigation_pve()); // % calculation Mitigation % vs PvE 392 = 39.2%// confirmed DoV
packet->setDataByName("mitigation_pct_pvp", info_struct->get_mitigation_pvp()); // % calculation Mitigation % vs PvP 559 = 55.9%// confirmed DoV
packet->setDataByName("toughness", 0);//toughness// confirmed DoV
packet->setDataByName("toughness_resist_dmg_pvp", 0);//toughness_resist_dmg_pvp 73 = 7300% // confirmed DoV
packet->setDataByName("avoidance_pct", (int16)info_struct->get_avoidance_display()*10.0f);//avoidance_pct 192 = 19.2% // confirmed DoV
@ -5058,10 +5060,10 @@ map<int32, Quest*>* Player::GetCompletedPlayerQuests(){
}
Quest* Player::GetAnyQuest(int32 quest_id) {
if(completed_quests.count(quest_id) > 0)
return completed_quests[quest_id];
if(player_quests.count(quest_id) > 0)
return player_quests[quest_id];
if(completed_quests.count(quest_id) > 0)
return completed_quests[quest_id];
return 0;
}
@ -5120,7 +5122,12 @@ int8 Player::CheckQuestFlag(Spawn* spawn){
}
}
MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__);
if (CanReceiveQuest(quests->at(i))){
int8 flag = 0;
if (CanReceiveQuest(quests->at(i), &flag)){
if(flag) {
ret = flag;
break;
}
master_quest_list.LockQuests();
quest = master_quest_list.GetQuest(quests->at(i), false);
master_quest_list.UnlockQuests();
@ -5155,7 +5162,7 @@ int8 Player::CheckQuestFlag(Spawn* spawn){
return ret;
}
bool Player::CanReceiveQuest(int32 quest_id){
bool Player::CanReceiveQuest(int32 quest_id, int8* ret){
bool passed = true;
int32 x;
master_quest_list.LockQuests();
@ -5266,6 +5273,18 @@ bool Player::CanReceiveQuest(int32 quest_id){
passed = false;
}
}
int32 flag = 0;
if(lua_interface->CallQuestFunction(quest, "ReceiveQuestCriteria", this, 0xFFFFFFFF, &flag)) {
if(ret)
*ret = flag;
if(!flag) {
passed = false;
}
else {
passed = true;
}
}
return passed;
}

View file

@ -265,7 +265,7 @@ public:
void RemoveEquipmentUpdates()
{
appearanceList->clear();
appearanceList->clear();
safe_delete(appearanceList);
}
@ -865,7 +865,7 @@ public:
PlayerLanguagesList* GetPlayerLanguages() { return &player_languages_list; }
bool HasLanguage(int32 id);
bool HasLanguage(const char* name);
bool CanReceiveQuest(int32 quest_id);
bool CanReceiveQuest(int32 quest_id, int8* ret = 0);
float GetBoatX() { if (info) return info->GetBoatX(); return 0; }
float GetBoatY() { if (info) return info->GetBoatY(); return 0; }
float GetBoatZ() { if (info) return info->GetBoatZ(); return 0; }
@ -1051,6 +1051,9 @@ public:
int32 GetCurrentLanguage() { return current_language_id; }
void SetCurrentLanguage(int32 language_id) { current_language_id = language_id; }
void SetActiveReward(bool val) { active_reward = val; }
bool IsActiveReward() { return active_reward; }
Mutex MPlayerQuests;
float pos_packet_speed;
private:
@ -1185,6 +1188,8 @@ private:
vector<GMTagFilter> gm_visual_filters;
int32 current_language_id;
bool active_reward;
};
#pragma pack()
#endif

View file

@ -1475,6 +1475,13 @@ void Quest::AddTmpRewardItem(Item* item){
tmp_reward_items.push_back(item);
}
void Quest::GetTmpRewardItemsByID(std::vector<int32>* items) {
if(!items)
return;
for(int32 i=0;i<tmp_reward_items.size();i++)
items->push_back(tmp_reward_items[i]->details.item_id);
}
void Quest::AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat){
reward_coins = copper + (silver*100) + (gold*10000) + ((int64)plat*1000000);
}

View file

@ -140,6 +140,7 @@ public:
void AddRewardItem(Item* item);
void AddTmpRewardItem(Item* item);
void GetTmpRewardItemsByID(std::vector<int32>* items);
void AddSelectableRewardItem(Item* item);
void AddRewardCoins(int32 copper, int32 silver, int32 gold, int32 plat);
void AddRewardCoinsMax(int64 coins);

View file

@ -224,6 +224,7 @@ void RuleManager::Init()
RULE_INIT(R_PVP, AllowPVP, "0");
RULE_INIT(R_PVP, LevelRange, "4");
RULE_INIT(R_PVP, InvisPlayerDiscoveryRange, "20"); // value > 0 sets radius inner to see, = 0 means always seen, -1 = never seen
RULE_INIT(R_PVP, PVPMitigationModByLevel, "25"); // gives a bonus to mitigation for PVP combat to offset the percentage level * mod (default 25)
/* COMBAT */
RULE_INIT(R_Combat, MaxCombatRange, "4.0");
@ -237,6 +238,12 @@ void RuleManager::Init()
RULE_INIT(R_Combat, SpiritShardSpawnScript, "SpawnScripts/Generic/SpiritShard.lua");
RULE_INIT(R_Combat, ShardDebtRecoveryPercent, "25.00"); // recovered percentage of debt upon obtainig shard, 25/100 means 25%. If there is .5 DeathExperienceDebt, .5*25% = .125, .5 - .125 = .375
RULE_INIT(R_Combat, ShardRecoveryByRadius, "1"); // allow shards to auto pick up by radius, not requiring to click/right click the shard
RULE_INIT(R_Combat, EffectiveMitigationCapLevel, "80"); // level multiplier for max effective cap, level * 80 (default)
RULE_INIT(R_Combat, CalculatedMitigationCapLevel, "100"); // The cap to calculate your mitigation from is [level*100].
RULE_INIT(R_Combat, MitigationLevelEffectivenessMax, "1.5"); // ratio victim level / attacker level for max effectiveness, when victim is higher level cap can reach 1.5
RULE_INIT(R_Combat, MitigationLevelEffectivenessMin, ".5"); // ratio victim level / attacker level for min effectiveness
RULE_INIT(R_Combat, MaxMitigationAllowed, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVE
RULE_INIT(R_Combat, MaxMitigationAllowedPVP, ".75"); // percentage max mitigation allowed, eg. 75% of damage can be mitigated max in PVP
/* SPAWN */
RULE_INIT(R_Spawn, SpeedMultiplier, "300"); // note: this value was 1280 until 6/1/2009, then was 600 til Sep 2009, when it became 300...?

View file

@ -83,6 +83,7 @@ enum RuleType {
AllowPVP,
LevelRange,
InvisPlayerDiscoveryRange,
PVPMitigationModByLevel,
/* COMBAT */
MaxCombatRange,
@ -96,6 +97,12 @@ enum RuleType {
SpiritShardSpawnScript,
ShardDebtRecoveryPercent,
ShardRecoveryByRadius,
EffectiveMitigationCapLevel,
CalculatedMitigationCapLevel,
MitigationLevelEffectivenessMax,
MitigationLevelEffectivenessMin,
MaxMitigationAllowed,
MaxMitigationAllowedPVP,
/* SPAWN */
SpeedMultiplier,

View file

@ -1833,6 +1833,88 @@ SOGA chars looked ok in LoginServer screen tho... odd.
return false;
}
void WorldDatabase::LoadCharacterQuestRewards(Client* client) {
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description FROM character_quest_rewards where char_id = %u ORDER BY indexed asc", client->GetCharacterID());
int8 count = 0;
if(result)
{
while(result && (row = mysql_fetch_row(result)))
{
int32 index = atoul(row[0]);
int32 quest_id = atoul(row[1]);
bool is_temporary = atoul(row[2]);
bool is_collection = atoul(row[3]);
bool has_displayed = atoul(row[4]);
int64 tmp_coin = 0;
#ifdef WIN32
tmp_coin = _strtoui64(row[5], NULL, 10);
#else
tmp_coin = strtoull(row[5], 0, 10);
#endif
int32 tmp_status = atoul(row[6]);
std::string description = std::string("");
if(row[7]) {
std::string description = std::string(row[7]);
}
if(is_collection) {
map<int32, Collection*>* collections = client->GetPlayer()->GetCollectionList()->GetCollections();
map<int32, Collection*>::iterator itr;
Collection* collection = 0;
for (itr = collections->begin(); itr != collections->end(); itr++) {
collection = itr->second;
if (collection->GetIsReadyToTurnIn()) {
client->GetPlayer()->SetPendingCollectionReward(collection);
break;
}
}
}
if(is_temporary) {
LoadCharacterQuestTemporaryRewards(client, quest_id);
}
client->QueueQuestReward(quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description, true, index);
count++;
}
}
if(count) {
client->SetQuestUpdateState(true);
}
}
void WorldDatabase::LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id) {
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", client->GetCharacterID(), quest_id);
int8 count = 0;
if(result)
{
while(result && (row = mysql_fetch_row(result)))
{
int32 item_id = atoul(row[0]);
Quest* quest = client->GetPlayer()->GetAnyQuest(quest_id);
if(quest) {
Item* item = master_item_list.GetItem(item_id);
if(item) {
quest->AddTmpRewardItem(new Item(item));
}
}
}
}
}
bool WorldDatabase::InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id){
Query query1;
Query query2;
@ -4085,6 +4167,7 @@ void WorldDatabase::Save(Client* client){
SavePlayerSpells(client);
SavePlayerMail(client);
SavePlayerCollections(client);
client->SaveQuestRewardData();
LogWrite(PLAYER__INFO, 3, "Player", "Player '%s' (%u) data saved.", player->GetName(), player->GetCharacterID());
}
@ -4924,6 +5007,9 @@ bool WorldDatabase::DeleteCharacter(int32 account_id, int32 character_id){
query.RunQuery2(Q_DELETE, "DELETE FROM characters WHERE id=%u AND account_id=%u", character_id, account_id);
//delete languages
query2.RunQuery2(Q_DELETE, "DELETE FROM character_languages WHERE char_id=%u", character_id);
//delete quest rewards
query2.RunQuery2(Q_DELETE, "DELETE FROM character_quest_rewards WHERE char_id=%u", character_id);
query2.RunQuery2(Q_DELETE, "DELETE FROM character_quest_temporary_rewards WHERE char_id=%u", character_id);
if(!query.GetAffectedRows())
{

View file

@ -296,6 +296,8 @@ public:
void LoadCharacterItemList(int32 account_id, int32 char_id, Player* player, int16);
bool loadCharacter(const char* name, int32 account_id, Client* client);
bool LoadCharacterStats(int32 id, int32 account_id, Client* client);
void LoadCharacterQuestRewards(Client* client);
void LoadCharacterQuestTemporaryRewards(Client* client, int32 quest_id);
bool InsertCharacterStats(int32 character_id, int8 class_id, int8 race_id);
bool UpdateCharacterTimeStamp(int32 account_id, int32 character_id, int32 timestamp);
bool insertCharacterProperty(Client* client, char* propName, char* propValue);

View file

@ -402,6 +402,7 @@ void Client::SendLoginInfo() {
}
database.LoadPlayerFactions(this);
database.LoadCharacterQuests(this);
database.LoadCharacterQuestRewards(this);
database.LoadPlayerMail(this);
}
LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
@ -5444,14 +5445,75 @@ void Client::AddPendingQuestAcceptReward(Quest* quest)
MPendingQuestAccept.unlock();
}
void Client::AddPendingQuestReward(Quest* quest, bool update) {
MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
quest_pending_reward.push_back(quest->GetQuestID());
void Client::AddPendingQuestReward(Quest* quest, bool update, bool is_temporary, std::string description) {
QueueQuestReward(quest->GetQuestID(), is_temporary, false, false, (is_temporary ? quest->GetCoinTmpReward() : 0),
(is_temporary ? quest->GetStatusTmpReward() : 0), description, false, 0);
quest_updates = update;
MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
if(quest_updates) {
SaveQuestRewardData(true);
}
}
void Client::QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved, int32 index) {
if(HasQuestRewardQueued(quest_id, is_temporary, is_collection))
return;
QuestRewardData data;
data.quest_id = quest_id;
data.is_temporary = is_temporary;
data.is_collection = is_collection;
data.has_displayed = has_displayed;
data.tmp_coin = tmp_coin;
data.tmp_status = tmp_status;
data.description = std::string(description);
data.db_saved = db_saved;
data.db_index = index;
MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
quest_pending_reward.push_back(data);
MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
}
bool Client::HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection) {
bool success = false;
MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__);
if (quest_pending_reward.size() > 0) {
vector<QuestRewardData>::iterator itr;
for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) {
int32 questID = (*itr).quest_id;
bool temporary = (*itr).is_temporary;
bool collection = (*itr).is_collection;
if( questID == quest_id && is_temporary == temporary && is_collection == collection ) {
success = true;
break;
}
}
}
MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
return success;
}
void Client::RemoveQueuedQuestReward() {
MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
if(quest_pending_reward.size() > 0) {
QuestRewardData data = quest_pending_reward.at(0);
if(data.db_saved) {
Query query;
query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_rewards where char_id = %u and indexed = %u", GetCharacterID(), data.db_index);
if(data.is_temporary && data.quest_id) {
query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete FROM character_quest_temporary_rewards where char_id = %u and quest_id = %u", GetCharacterID(), data.quest_id);
}
}
quest_pending_reward.erase(quest_pending_reward.begin());
}
MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
SaveQuestRewardData(true);
}
void Client::AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress) {
MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
quest_pending_updates[quest_id][step_id] = progress;
@ -5461,6 +5523,9 @@ void Client::AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress
}
void Client::ProcessQuestUpdates() {
if(!GetPlayer()->IsFullyLoggedIn())
return;
if (quest_pending_updates.size() > 0) {
map<int32, map<int32, int32> > tmp_quest_updates;
MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
@ -5483,17 +5548,41 @@ void Client::ProcessQuestUpdates() {
MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__);
if (quest_pending_reward.size() > 0) {
MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
vector<int32>::iterator itr;
vector<int32> tmp_quest_rewards;
// only able to display one reward at a time
if(GetPlayer()->IsActiveReward())
return;
Query query;
vector<QuestRewardData>::iterator itr;
vector<QuestRewardData> tmp_quest_rewards;
MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
tmp_quest_rewards.insert(tmp_quest_rewards.begin(), quest_pending_reward.begin(), quest_pending_reward.end());
quest_pending_reward.clear();
tmp_quest_rewards.insert(tmp_quest_rewards.begin(), quest_pending_reward.begin(), quest_pending_reward.begin()+1);
MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
for (itr = tmp_quest_rewards.begin(); itr != tmp_quest_rewards.end(); itr++) {
int32 questID = *itr;
Quest* quest = GetPlayer()->GetAnyQuest(questID);
if(quest) {
GiveQuestReward(quest);
int32 questID = (*itr).quest_id;
Quest* quest = 0;
if((*itr).is_collection && GetPlayer()->GetPendingCollectionReward()) {
DisplayCollectionComplete(GetPlayer()->GetPendingCollectionReward());
GetPlayer()->SetActiveReward(true);
(*itr).has_displayed = true;
UpdateCharacterRewardData(&(*itr));
}
else if(questID > 0 && (quest = GetPlayer()->GetAnyQuest(questID))) {
quest->SetQuestTemporaryState((*itr).is_temporary, (*itr).description);
if((*itr).is_temporary) {
quest->SetStatusTmpReward((*itr).tmp_status);
quest->SetCoinTmpReward((*itr).tmp_coin);
}
GiveQuestReward(quest, (*itr).has_displayed);
GetPlayer()->SetActiveReward(true);
(*itr).has_displayed = true;
UpdateCharacterRewardData(&(*itr));
// only able to display one reward at a time
break;
} else {
LogWrite(CCLIENT__ERROR, 0, "Client", "Quest ID %u missing for Player %s, skipping quest id from tmp_quest_rewards.", questID, GetPlayer()->GetName());
}
@ -5501,7 +5590,15 @@ void Client::ProcessQuestUpdates() {
} else {
MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
}
quest_updates = false;
MQuestPendingUpdates.readlock(__FUNCTION__, __LINE__);
if (quest_pending_reward.size() > 0) {
quest_updates = true;
}
else {
quest_updates = false;
}
MQuestPendingUpdates.releasereadlock(__FUNCTION__, __LINE__);
}
@ -5961,7 +6058,11 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) {
totalItems += items->size();
}
}
RemoveQueuedQuestReward();
GetPlayer()->SetActiveReward(false);
if (free_slots >= num_slots_needed || (player->item_list.HasFreeBagSlot() && master_item && master_item->IsBag() && master_item->bag_info->num_slots >= totalItems)) {
if (master_item)
AddItem(item_id);
@ -5994,31 +6095,34 @@ void Client::AcceptQuestReward(Quest* quest, int32 item_id) {
AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
player->GetInfoStruct()->add_status_points(quest->GetStatusTmpReward());
quest->SetQuestTemporaryState(false);
}
else
player->GetInfoStruct()->add_status_points(quest->GetStatusPoints());
else {
player->GetInfoStruct()->add_status_points(quest->GetStatusPoints());
}
quest->SetQuestTemporaryState(false);
player->SetCharSheetChanged(true);
}
else {
MPendingQuestAccept.lock();
pending_quest_accept.push_back(quest->GetQuestID());
MPendingQuestAccept.unlock();
GetPlayer()->SetActiveReward(true);
AddPendingQuestAcceptReward(quest);
SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots! Free some slots and try again.");
DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription());
}
}
void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards, vector<Item*>* selectable_rewards, map<int32, sint32>* factions, const char* header, int32 status_points, const char* text) {
void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards, vector<Item*>* selectable_rewards, map<int32, sint32>* factions, const char* header, int32 status_points, const char* text, bool was_displayed) {
if (coin == 0 && (!rewards || rewards->size() == 0) && (!selectable_rewards || selectable_rewards->size() == 0) && (!factions || factions->size() == 0) && status_points == 0 && text == 0 && (!quest || (quest->GetCoinsReward() == 0 && quest->GetCoinsRewardMax() == 0))) {
/*if (quest)
text = quest->GetName();
else*/
return;//nothing to give
}
GetPlayer()->ClearPendingSelectableItemRewards(0, true);
GetPlayer()->ClearPendingItemRewards();
PacketStruct* packet2 = configReader.getStruct("WS_QuestRewardPackMsg", GetVersion());
if (packet2) {
int32 source_id = 0;
@ -6036,7 +6140,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
}
if (rewarded_coin > coin)
coin = rewarded_coin;
if (!quest) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
if (!quest && !was_displayed) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
if (coin > 0) {
player->AddCoins(coin);
PlaySound("coin_cha_ching");
@ -6045,7 +6149,7 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
packet2->setSubstructDataByName("reward_data", "unknown1", 255);
packet2->setSubstructDataByName("reward_data", "reward", header);
packet2->setSubstructDataByName("reward_data", "max_coin", coin);
if (player->GetGuild()) {
if (player->GetGuild() && !was_displayed) {
if (!quest) { //this entire function is either for version <=546 or for quest rewards in middle of quest, so quest should be 0, otherwise quest will handle the rewards
player->GetInfoStruct()->add_status_points(status_points);
player->SetCharSheetChanged(true);
@ -6107,16 +6211,12 @@ void Client::DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* reward
}
}
void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription) {
void Client::DisplayQuestComplete(Quest* quest, bool tempReward, std::string customDescription, bool was_displayed) {
if (!quest)
return;
quest->SetQuestTemporaryState(tempReward, customDescription);
AddPendingQuestAcceptReward(quest);
if (GetVersion() <= 546) {
DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints());
DisplayQuestRewards(quest, 0, quest->GetRewardItems(), quest->GetSelectableRewardItems(), quest->GetRewardFactions(), "Quest Complete!", quest->GetStatusPoints(), customDescription.c_str(), was_displayed);
return;
}
PacketStruct* packet = configReader.getStruct("WS_QuestComplete", GetVersion());
@ -6289,51 +6389,59 @@ void Client::DisplayRandomizeFeatures(int32 flags) {
}
void Client::GiveQuestReward(Quest* quest) {
void Client::GiveQuestReward(Quest* quest, bool has_displayed) {
current_quest_id = 0;
if(!quest->GetQuestTemporaryState())
if(!quest->GetQuestTemporaryState() && !has_displayed)
{
quest->IncrementCompleteCount();
player->AddCompletedQuest(quest);
}
AddPendingQuestAcceptReward(quest);
DisplayQuestComplete(quest, quest->GetQuestTemporaryState(), quest->GetQuestTemporaryDescription());
LogWrite(CCLIENT__DEBUG, 0, "Client", "Send Quest Journal...");
SendQuestJournal();
if(quest->GetQuestTemporaryState())
if(quest->GetQuestTemporaryState()) {
return;
player->RemoveQuest(quest->GetQuestID(), false);
if (quest->GetExpReward() > 0) {
int16 level = player->GetLevel();
int32 xp = quest->GetExpReward();
if (player->AddXP(xp)) {
Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp);
if (player->GetLevel() != level)
ChangeLevel(level, player->GetLevel());
player->SetCharSheetChanged(true);
}
}
if (quest->GetTSExpReward() > 0) {
int8 ts_level = player->GetTSLevel();
int32 xp = quest->GetTSExpReward();
if (player->AddTSXP(xp)) {
Message(CHANNEL_REWARD, "You gain %u tradeskill experience!", (int32)xp);
if (player->GetTSLevel() != ts_level)
ChangeTSLevel(ts_level, player->GetTSLevel());
player->SetCharSheetChanged(true);
if(!has_displayed) {
if (quest->GetExpReward() > 0) {
int16 level = player->GetLevel();
int32 xp = quest->GetExpReward();
if (player->AddXP(xp)) {
Message(CHANNEL_REWARD, "You gain %u experience!", (int32)xp);
if (player->GetLevel() != level)
ChangeLevel(level, player->GetLevel());
player->SetCharSheetChanged(true);
}
}
if (quest->GetTSExpReward() > 0) {
int8 ts_level = player->GetTSLevel();
int32 xp = quest->GetTSExpReward();
if (player->AddTSXP(xp)) {
Message(CHANNEL_REWARD, "You gain %u tradeskill experience!", (int32)xp);
if (player->GetTSLevel() != ts_level)
ChangeTSLevel(ts_level, player->GetTSLevel());
player->SetCharSheetChanged(true);
}
}
int64 total_coins = quest->GetGeneratedCoin();
if (total_coins > 0)
AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
player->RemoveQuest(quest->GetQuestID(), false);
}
int64 total_coins = quest->GetGeneratedCoin();
if (total_coins > 0)
AwardCoins(total_coins, std::string("for completing ").append(quest->GetName()));
if (quest->GetQuestGiver() > 0)
GetCurrentZone()->SendSpawnChangesByDBID(quest->GetQuestGiver(), this, false, true);
RemovePlayerQuest(quest->GetQuestID(), true, false);
if(!has_displayed) {
RemovePlayerQuest(quest->GetQuestID(), true, false);
}
}
void Client::DisplayConversation(int32 conversation_id, int32 spawn_id, vector<ConversationOption>* conversations, const char* text, const char* mp3, int32 key1, int32 key2, int8 language, int8 can_close) {
@ -8805,6 +8913,7 @@ void Client::SetReadyForSpawns(bool val) {
world.GetGroupManager()->GroupMessage(GetPlayer()->GetGroupMemberInfo()->group_id, "%s has returned from Linkdead.", GetPlayer()->GetName());
}
}
GetPlayer()->SetActiveReward(false);
zone_list.CheckFriendZoned(this);
}
@ -9043,9 +9152,8 @@ void Client::InspectPlayer(Player* player_to_inspect) {
packet->setDataByName("gender", player_to_inspect->GetGender());
packet->setDataByName("adventure_level", player_to_inspect->GetLevel());
LogWrite(MISC__TODO, 1, "TODO", "Put mentored level here (adventure_level_effective)\nfile: %s, func: %s, line: %i", __FILE__, __FUNCTION__, __LINE__);
packet->setDataByName("adventure_level_effective", player_to_inspect->GetLevel());
int16 effective_level = player_to_inspect->GetInfoStruct()->get_effective_level() != 0 ? player_to_inspect->GetInfoStruct()->get_effective_level() : player_to_inspect->GetLevel();
packet->setDataByName("adventure_level_effective", effective_level);
packet->setDataByName("adventure_class", player_to_inspect->GetAdventureClass());
packet->setDataByName("tradeskill_level", player_to_inspect->GetTSLevel());
packet->setDataByName("tradeskill_class", player_to_inspect->GetTradeskillClass());
@ -9543,11 +9651,26 @@ void Client::HandInCollections() {
collection = itr->second;
if (collection->GetIsReadyToTurnIn()) {
player->SetPendingCollectionReward(collection);
DisplayCollectionComplete(collection);
return;
MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
QuestRewardData data;
data.quest_id = 0;
data.is_temporary = false;
data.description = std::string("");
data.is_collection = true;
data.has_displayed = false;
data.tmp_coin = 0;
data.tmp_status = 0;
data.db_saved = false;
data.db_index = 0;
quest_pending_reward.push_back(data);
MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
quest_updates = true;
break;
}
}
if(quest_updates) {
SaveQuestRewardData(true);
}
}
void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_item_id) {
@ -9567,8 +9690,7 @@ void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_it
num_slots = player->GetPlayerItemList()->GetNumberOfFreeSlots();
if (num_slots < num_slots_needed) {
SimpleMessage(CHANNEL_COLOR_RED, "You do not have enough free slots. Free up some slots and try again");
HandInCollections();
DisplayCollectionComplete(collection);
return;
}
@ -9606,6 +9728,11 @@ void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_it
/* reset the pending collection reward and check for my collections that the player needs to hand in */
player->SetPendingCollectionReward(0);
RemoveQueuedQuestReward();
GetPlayer()->SetActiveReward(false);
HandInCollections();
}
@ -11041,4 +11168,56 @@ void Client::SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_ga
if(resStruct) {
GetPlayer()->GetZone()->PlayFlavor(this, spawn, resStruct->mp3_string.c_str(), resStruct->text_string.c_str(), resStruct->emote_string.c_str(), resStruct->key1, resStruct->key2, language);
}
}
void Client::SaveQuestRewardData(bool force_refresh) {
Query query;
if(force_refresh) {
query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_rewards where char_id = %u",
GetCharacterID());
query.AddQueryAsync(GetCharacterID(), &database, Q_DELETE, "delete from character_quest_temporary_rewards where char_id = %u",
GetCharacterID());
}
vector<QuestRewardData>::iterator itr;
vector<QuestRewardData> tmp_quest_rewards;
MQuestPendingUpdates.writelock(__FUNCTION__, __LINE__);
int index = 0;
for (itr = quest_pending_reward.begin(); itr != quest_pending_reward.end(); itr++) {
int32 questID = (*itr).quest_id;
if(!(*itr).db_saved || force_refresh) {
query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_rewards (char_id, indexed, quest_id, is_temporary, is_collection, has_displayed, tmp_coin, tmp_status, description) values(%u, %u, %u, %u, %u, %u, %I64u, %u, '%s')",
GetCharacterID(), index, questID, (*itr).is_temporary, (*itr).is_collection, (*itr).has_displayed, (*itr).tmp_coin, (*itr).tmp_status, database.getSafeEscapeString((*itr).description.c_str()).c_str());
(*itr).db_saved = true;
(*itr).db_index = index;
if((*itr).is_temporary) {
std::vector<int32> items;
Quest* quest = GetPlayer()->GetAnyQuest(questID);
if(quest) {
quest->GetTmpRewardItemsByID(&items);
if(!force_refresh && items.size() > 0) {
query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "delete from character_quest_temporary_rewards where char_id = %u and quest_id = %u",
GetCharacterID(), questID);
}
for(int i=0;i<items.size();i++) {
query.AddQueryAsync(GetCharacterID(), &database, Q_REPLACE, "replace into character_quest_temporary_rewards (char_id, quest_id, item_id) values(%u, %u, %u)",
GetCharacterID(), questID, items[i]);
}
}
}
}
index++;
}
MQuestPendingUpdates.releasewritelock(__FUNCTION__, __LINE__);
}
void Client::UpdateCharacterRewardData(QuestRewardData* data) {
if(!data)
return;
if(data->db_saved) {
Query query;
query.AddQueryAsync(GetCharacterID(), &database, Q_INSERT, "update character_quest_rewards set is_temporary = %u, is_collection = %u, has_displayed = %u, tmp_coin = %I64u, tmp_status = %u, description = '%s' where char_id=%u and indexed=%u and quest_id=%u",
data->is_temporary, data->is_collection, data->has_displayed, data->tmp_coin, data->tmp_status, database.getSafeEscapeString(data->description.c_str()).c_str(), GetCharacterID(), data->db_index, data->quest_id);
}
}

View file

@ -142,6 +142,18 @@ struct WaypointInfo {
int8 type;
};
struct QuestRewardData {
int32 quest_id;
bool is_temporary;
std::string description;
bool is_collection;
bool has_displayed;
int64 tmp_coin;
int32 tmp_status;
bool db_saved;
int32 db_index;
};
class Client {
public:
Client(EQStream* ieqs);
@ -280,8 +292,8 @@ public:
void SendQuestFailure(Quest* quest);
void SendQuestUpdateStep(Quest* quest, int32 step, bool display_quest_helper = true);
void SendQuestUpdateStepImmediately(Quest* quest, int32 step, bool display_quest_helper = true);
void DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards=0, vector<Item*>* selectable_rewards=0, map<int32, sint32>* factions=0, const char* header="Quest Reward!", int32 status_points=0, const char* text=0);
void DisplayQuestComplete(Quest* quest, bool tempReward = false, std::string customDescription = string(""));
void DisplayQuestRewards(Quest* quest, int64 coin, vector<Item*>* rewards=0, vector<Item*>* selectable_rewards=0, map<int32, sint32>* factions=0, const char* header="Quest Reward!", int32 status_points=0, const char* text=0, bool was_displayed = false);
void DisplayQuestComplete(Quest* quest, bool tempReward = false, std::string customDescription = string(""), bool was_displayed = false);
void DisplayRandomizeFeatures(int32 features);
void AcceptQuestReward(Quest* quest, int32 item_id);
Quest* GetPendingQuestAcceptance(int32 item_id);
@ -348,7 +360,10 @@ public:
void SetPlayer(Player* new_player);
void AddPendingQuestAcceptReward(Quest* quest);
void AddPendingQuestReward(Quest* quest, bool update=true);
void AddPendingQuestReward(Quest* quest, bool update=true, bool is_temporary = false, std::string description = std::string(""));
bool HasQuestRewardQueued(int32 quest_id, bool is_temporary, bool is_collection);
void QueueQuestReward(int32 quest_id, bool is_temporary, bool is_collection, bool has_displayed, int64 tmp_coin, int32 tmp_status, std::string description, bool db_saved=false, int32 index=0);
void RemoveQueuedQuestReward();
void AddPendingQuestUpdate(int32 quest_id, int32 step_id, int32 progress = 0xFFFFFFFF);
void ProcessQuestUpdates();
void AddWaypoint(const char* waypoint_name, int8 waypoint_category, int32 spawn_id);
@ -543,15 +558,18 @@ public:
bool UseItem(Item* item, Spawn* target = nullptr);
void SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, VoiceOverStruct* garble, bool success = false, bool garble_success = false);
void SaveQuestRewardData(bool force_refresh = false);
void UpdateCharacterRewardData(QuestRewardData* data);
void SetQuestUpdateState(bool val) { quest_updates = val; }
private:
void SavePlayerImages();
void SkillChanged(Skill* skill, int16 previous_value, int16 new_value);
void GiveQuestReward(Quest* quest);
void GiveQuestReward(Quest* quest, bool has_displayed = false);
void SetStepComplete(int32 quest_id, int32 step);
void AddStepProgress(int32 quest_id, int32 step, int32 progress);
map<int32, map<int32, int32> > quest_pending_updates;
vector<QueuedQuest*> quest_queue;
vector<int32> quest_pending_reward;
vector<QuestRewardData> quest_pending_reward;
volatile bool quest_updates;
Mutex MQuestPendingUpdates;
Mutex MQuestQueue;