bool Client::TrainDiscipline(uint32 itemid) { //get the item info const Item_Struct *item = database.GetItem(itemid); if(item == NULL) { Message(13, "Unable to find the tome you turned in!"); LogFile->write(EQEMuLog::Error, "Unable to find turned in tome id %lu\n", (unsigned long)itemid); return(false); } if(item->ItemClass != ItemClassCommon || item->ItemType != ItemTypeSpell) { Message(13, "Invalid item type, you cannot learn from this item."); //summon them the item back... SummonItem(itemid); return(false); } //Need a way to determine the difference between a spell and a tome //so they cant turn in a spell and get it as a discipline //this is kinda a hack: if(!( item->Name[0] == 'T' && item->Name[1] == 'o' && item->Name[2] == 'm' && item->Name[3] == 'e' && item->Name[4] == ' ' )) { Message(13, "This item is not a tome."); //summon them the item back... SummonItem(itemid); return(false); } int myclass = GetClass(); if(myclass == WIZARD || myclass == ENCHANTER || myclass == MAGICIAN || myclass == NECROMANCER) { Message(13, "Your class cannot learn from this tome."); //summon them the item back... SummonItem(itemid); return(false); } //make sure we can train this... //can we use the item? uint32 cbit = 1 << (myclass-1); if(!(item->Classes & cbit)) { Message(13, "Your class cannot learn from this tome."); //summon them the item back... SummonItem(itemid); return(false); } uint32 spell_id = item->Scroll.Effect; if(!IsValidSpell(spell_id)) { Message(13, "This tome contains invalid knowledge."); return(false); } //can we use the spell? const SPDat_Spell_Struct &spell = spells[spell_id]; uint8 level_to_use = spell.classes[myclass - 1]; if(level_to_use == 255) { Message(13, "Your class cannot learn from this tome."); //summon them the item back... SummonItem(itemid); return(false); } if(level_to_use > GetLevel()) { Message(13, "You must be at least level %d to learn this discipline.", level_to_use); //summon them the item back... SummonItem(itemid); return(false); } //add it to PP. int r; for(r = 0; r < MAX_PP_DISCIPLINES; r++) { if(m_pp.disciplines.values[r] == spell_id) { Message(13, "You already know this discipline."); //summon them the item back... SummonItem(itemid); return(false); } else if(m_pp.disciplines.values[r] == 0) { m_pp.disciplines.values[r] = spell_id; SendDisciplineUpdate(); Message(0, "You have learned a new discipline!"); return(true); } } Message(13, "You have learned too many disciplines and can learn no more."); return(false); }
void Client::CheckQuests(const char* zonename, const char * message, int32 npc_id, uint16 item_id, Mob* other) { bool ps = false; bool tt = false; bool ti = false; bool tt2 = false; bool ti2 = false; bool stt = false; bool std = false; bool td = false; bool sta = false; bool ta = false; bool sti = false; bool stf = false; bool tf = false; bool tk = false; bool stk = false; bool levelcheck = false; int8 fac = GetFactionLevel(GetID(),other->GetNPCTypeID(), GetRace(), GetClass(), DEITY_AGNOSTIC, other->CastToNPC()->GetPrimaryFaction(), other); FILE * pFile; long lSize; char * buffer; char filename[255]; #ifdef WIN32 snprintf(filename, 254, "quests\\%i.qst", npc_id); #else snprintf(filename, 254, "quests/%i.qst", npc_id); #endif adverrorinfo = 940; if ((pFile=fopen(filename,"rb"))) { #ifdef DEBUG printf("Reading quests from %s\n", filename); #endif } else { #ifdef DEBUG printf("Error: No quests file found for %s\n", filename); #endif return; } if (pFile==NULL) exit (1); // obtain file size. fseek (pFile , 0 , SEEK_END); lSize = ftell (pFile); rewind (pFile); adverrorinfo = 941; // allocate memory to contain the whole file. buffer = (char*) malloc (lSize); if (buffer == NULL) exit (2); // copy the file into the buffer. fread (buffer,1,lSize,pFile); fclose(pFile); Seperator3 sep3(buffer); for(int i=0; i < 2048; ++i) { // printf("Temp: %s\n", sep3.arghz[i]); char * command; char * temp; temp = sep3.arghz[i]; if (temp == NULL) return; Seperator sep(temp); Seperator4 sep4(temp); command = sep4.arghza[0]; command = strupr(command); #ifdef DEBUG cout<<sep.argplus[0]<<endl; #endif if (!IsCommented(temp)) { if (strstr(command,"END_FILE") != NULL || command == NULL) { break; } if (ti2 || tt2 || tf || td || ta) { char lvl[5]; sprintf(lvl, "%i", this->GetLevel()); strcpy(sep4.arghza[1], strreplace(sep4.arghza[1],"%CHARRACE%", GetRaceName(this->GetRace()))); strcpy(sep4.arghza[1], strreplace(sep4.arghza[1],"%CHARLEVEL%", lvl)); strcpy(sep4.arghza[1], strreplace(sep4.arghza[1],"%CHARCLASS%", GetEQClassName(this->GetClass(), 50))); char * nmessage = strreplace(sep4.arghza[1],"%CHARNAME%", this->GetName()); nmessage[strlen(nmessage) - 1] = '\0'; if (ti2 || tt2 || ta) { other->CastToNPC()->FaceTarget(this, true); } if ((fac == 7 || fac == 6) && (ti2 || tt2)) { entity_list.NPCMessage(other,true,200,0,"%s says, 'I will have nothing to do with such as you. Begone.'",other->GetName()); break; } else if (strstr(command,"FACTION_CHECK") != NULL) { int8 fac2 = atoi(sep.arg[1]); if ((fac2 <= 5 && fac > fac2) || (fac2 == 6 && fac == 8)) { entity_list.NPCMessage(other,true,200,0,"%s says, 'I will have nothing to do with such as you. Begone.'",other->GetName()); break; } ps = true; } else if (strstr(command,"SAY") != NULL) { entity_list.NPCMessage(this, false, 400, 0, "%s says, '%s'", other->GetName(), nmessage); ps = true; } else if (strstr(command,"EMOTE") != NULL) { cout<<"Emoting!"<<endl; entity_list.NPCMessage(this, false, 400, 0, "%s %s", other->GetName(), nmessage); ps = true; } else if (strstr(command,"TEXT") != NULL) { entity_list.NPCMessage(this, false, 400, 0, "%s", nmessage); ps = true; } else if (strstr(command,"SHOUT") != NULL) { entity_list.NPCMessage(this, false, 0, 13, "%s shouts, '%s'", other->GetName(), nmessage); ps = true; } else if (strstr(command,"SPAWN_ITEM") != NULL) { this->SummonItem(atoi(sep.arg[1])); ps = true; } else if (strstr(command,"ADD_HATELIST") != NULL) { other->CastToNPC()->AddToHateList(this, 100); ps = true; } else if (strstr(command,"DEPOP") != NULL) { other->CastToNPC()->Depop(); ps = true; } #ifdef GUILDWARS else if (strstr(command,"CITYTAKE") != NULL) { if(IsClient() && GuildDBID() != 0) { if(target->CastToNPC()->GetGuildOwner() == GuildDBID()) { entity_list.NPCMessage(this, false, 400, 0, "%s says, 'You are a member of the guild that owns this city, why would you want to conquere it?'", other->GetName(), nmessage); } else if(!IsAttackAllowed(other)) { entity_list.NPCMessage(this, false, 400, 0, "%s says, 'You cannot conquere an allied city.'", other->GetName(), nmessage); } else if(database.CityDefense(zone->GetZoneID(),0)) { entity_list.NPCMessage(this, false, 400, 0, "%s says, 'This city still has men standing, I will not surrender!'", other->GetName(), nmessage); } else if(database.SetCityGuildOwned(GuildDBID(),other->GetNPCTypeID())) { entity_list.NPCMessage(this, false, 400, 0, "%s says, 'You have won, I surrender to you.'", other->GetName(), nmessage); zone->SetGuildOwned(GuildDBID()); zone->Repop(); } } ps = true; } #endif else if (strstr(command,"SPAWN_NPC") != NULL) { adverrorinfo = 751; const NPCType* tmp = 0; adverrorinfo = 752; int16 grid = atoi(sep.arg[2]); int8 guildwarset = atoi(sep.arg[3]); if ((tmp = database.GetNPCType(atoi(sep.arg[1])))) { adverrorinfo = 753; NPC* npc = new NPC(tmp, 0, other->GetX(), other->GetY(), other->GetZ(), heading); adverrorinfo = 754; npc->AddLootTable(); adverrorinfo = 755; entity_list.AddNPC(npc,true,true); adverrorinfo = 756; Sleep(200); if(npc != 0) { if(grid > 0) npc->AssignWaypoints(grid); adverrorinfo = 757; #ifdef GUILDWARS if(guildwarset > 0 && guildwarset == 1 && GuildDBID() > 0) npc->SetGuildOwner(GuildDBID()); #endif adverrorinfo = 758; npc->SendPosUpdate(); } } ps = true; } else if (strstr(command,"LEVEL_CHECK") != NULL) { int8 Level1 = atoi(sep.arg[1]); int8 Level2 = atoi(sep.arg[2]); int8 Levelp = this->GetLevel(); if(Level2 == 0 || Level2 == NULL){ //Min Level if(Level1 > Levelp) { //not high enough to talk too break; } } else{ //Level Range if(Level1 < Levelp && Level2 < Levelp) { //not in the req level range break; } } levelcheck = true; } else if (strstr(command,"CUMULATIVE_FLAG") != NULL) { other->flag[50] = other->flag[50] + 1; ps = true; } else if (strstr(command,"NPC_FLAG") != NULL) { other->flag[atoi(sep.arg[1])] = atoi(sep.arg[2]); ps = true; } else if (strstr(command,"PLAYER_FLAG") != NULL) { this->flag[atoi(sep.arg[1])] = atoi(sep.arg[2]); ps = true; } else if (strstr(command,"EXP") != NULL) { this->AddEXP (atoi(sep.arg[1])); ps = true; } else if (strstr(command,"LEVEL") != NULL) { this->SetLevel(atoi(sep.arg[1]), true); ps = true; } else if (strstr(command,"SAFEMOVE") != NULL) { this->MovePC(zone->GetShortName(),database.GetSafePoint(zone->GetShortName(),"x"),database.GetSafePoint(zone->GetShortName(),"y"),database.GetSafePoint(zone->GetShortName(),"z"),false,false); ps = true; } else if (strstr(command,"RAIN") != NULL) { zone->zone_weather = atoi(sep.arg[1]); APPLAYER* outapp = new APPLAYER; outapp = new APPLAYER; outapp->opcode = OP_Weather; outapp->pBuffer = new uchar[8]; memset(outapp->pBuffer, 0, 8); outapp->size = 8; outapp->pBuffer[4] = atoi(sep.arg[1]); // Why not just use 0x01/2/3? entity_list.QueueClients(this, outapp); delete outapp; ps = true; } else if (strstr(command,"SNOW") != NULL) { zone->zone_weather = atoi(sep.arg[1]) + 1; APPLAYER* outapp = new APPLAYER; outapp = new APPLAYER; outapp->opcode = OP_Weather; outapp->pBuffer = new uchar[8]; memset(outapp->pBuffer, 0, 8); outapp->size = 8; outapp->pBuffer[0] = 0x01; outapp->pBuffer[4] = atoi(sep.arg[1]); entity_list.QueueClients(this, outapp); delete outapp; ps = true; } else if (strstr(command,"GIVE_CASH") != NULL) { this->AddMoneyToPP(atoi(sep.arg[1]),true); ps = true; } else if (strstr(command,"GIVE_ITEM") != NULL) { int16 ItemID = atoi(sep.arg[1]); sint8 ItemCharges = atoi(sep.arg[2]); this->SummonItem(ItemID, ItemCharges); ps = true; } else if (strstr(command,"CAST_SPELL") != NULL) { other->CastSpell(atoi(sep.arg[1]),this->GetID()); ps = true; } else if (strstr(command,"PVP") != NULL) { if (strstr(strupr(sep.arg[1]),"ON") != NULL) this->CastToClient()->SetPVP(true); else this->CastToClient()->SetPVP(false); ps = true; } /*else if (strstr(command,"CHANGEFACTION") != NULL) { if ((sep.IsNumber(1)) && (sep.IsNumber(2))) { this->CastToClient()->SetFactionLevel2(this->CastToClient()->CharacterID(),atoi(sep.arg[1]), this->CastToClient()->GetClass(), this->CastToClient()->GetRace(), this->CastToClient()->GetDeity(), atoi(sep.arg[2])); this->CastToClient()->Message(0,BuildFactionMessage(GetCharacterFactionLevel(atoi(sep.arg[1])),atoi(sep.arg[1]))); } else { cout << "Error in script!!!!! Bad CHANGEFACTION Line! " << endl; } ps = true; }*/ else if (strstr(command,"DO_ANIMATION") != NULL) { other->DoAnim(atoi(sep.arg[1])); ps = true; } else if (strstr(command,"ADDSKILL") != NULL) { if (sep.IsNumber(1) && sep.IsNumber(2) && (atoi(sep.arg[2]) >= 0 || atoi(sep.arg[2]) <= 252) && (atoi(sep.arg[1]) >= 0 && atoi(sep.arg[1]) < 74)) { this->AddSkill(atoi(sep.arg[1]), atoi(sep.arg[2])); } else cout << "Error in script!!!! Bad ADDSKILL line!" << endl; ps = true; } else if (strstr(command,"FLAG_CHECK") != NULL) { if (this->flag[atoi(sep.arg[1])] == 0) break; else this->flag[atoi(sep.arg[1])] = 0; ps = true; } else if (strstr(command,"CHANCE") != NULL) { if (rand()%100 > atoi(sep.arg[1])) break; ps = true; } } if (IsEnd(temp)) { tt = false; tt2 = false; stt = false; ti = false; ti2 = false; sti = false; tk = false; td = false; std = false; ta = false; sta = false; tf = false; stf = false; stk = false; } if (strstr(command,"TRIGGER_ATTACK") != NULL) { if (strstr(message,"%%ATTACK%%") != NULL) { adverrorinfo = 943; ta = true; } else { sta = true; } } else if (strstr(command,"TRIGGER_DEATH") != NULL) { if (strstr(message,"%%DEATH%%") != NULL) { td = true; } else { std = true; } } else if (strstr(command,"TRIGGER_KILLED") != NULL) { if (strstr(message,"%%KILLED%%") != NULL) { tk = true; } else { stk = true; } } else if (strstr(command,"TRIGGER_FLAGS") != NULL) { int8 flag1 = atoi(sep.arg[1]); int8 flag2 = atoi(sep.arg[2]); int8 flag3 = atoi(sep.arg[3]); int8 flag4 = atoi(sep.arg[4]); if (flag1 == 50) { if (other->flag[50] >= atoi(sep.arg[2])) { tf = true; other->flag[50] = 0; } else stf = true; } else { if (ta || tt || ti) stf = true; else if (flag1 > 0 && other->flag[flag1] == 0) stf = true; else if (flag2 > 0 && other->flag[flag2] == 0) stf = true; else if (flag3 > 0 && other->flag[flag3] == 0) stf = true; else if (flag4 > 0 && other->flag[flag4] == 0) stf = true; else { tf = true; if (flag1 > 0) other->flag[flag1] = 0; if (flag2 > 0) other->flag[flag2] = 0; if (flag3 > 0) other->flag[flag3] = 0; if (flag4 > 0) other->flag[flag4] = 0; } } } else if (strstr(command,"TRIGGER_ITEM") != NULL) { if (!ta && !tt && !ti) { if ((uint16)item_id == atoi(sep4.arghza[1])) { ti = true; ti2 = true; } else { sti = true; } } else { printf("TRIGGER_ITEM Error at line %d: missing left curly bracket\n", i); return; } } else if (strstr(command,"TRIGGER_TEXT") != NULL) { if (strstr(message,"%%DEATH%%") == NULL && strstr(message,"%%ITEM%%") == NULL && strstr(message,"%%ATTACK%%") == NULL && strstr(message,"%%KILLED%%") == NULL && Dist(other) <= 50) { char* tmp = new char[strlen(message)+1]; strcpy(tmp, message); strupr(tmp); if (strstr(tmp,strupr(sep4.arghza[1])) != NULL) { tt2 = true; tt = true; } else { stt = true; } delete tmp; } else if (strstr(message,"%%DEATH%%") != NULL || strstr(message,"%%item%%") != NULL || strstr(message,"%%attack%%")) { stt = true; } else if (!ps) { printf("TRIGGER_TEXT Error at line %d: Not in NPC_Script\n", i); return; } else { printf("TRIGGER_TEXT Error at line %d: missing left curly bracket\n", i); return; } } /*#ifdef WIN32 delete command; delete temp; #endif*/ } } if (!ps && item_id != 0) { entity_list.NPCMessage(this,true,200,0,"%s has no use for this item.",other->GetName()); SummonItem(item_id); } delete buffer; }
//returns true on success bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { if(spec == NULL) return(false); uint16 user_skill = GetSkill(spec->tradeskill); float chance = 0.0; float skillup_modifier = 0.0; int16 thirdstat = 0; int16 stat_modifier = 15; uint16 success_modifier = 0; // Rework based on the info on eqtraders.com // http://mboards.eqtraders.com/eq/showthread.php?t=22246 // 09/10/2006 v0.1 (eq4me) // 09/11/2006 v0.2 (eq4me) // Todo: // Implementing AAs // Success modifiers based on recipes // Skillup modifiers based on the rarity of the ingredients // Some tradeskills are more eqal then others. ;-) // If you want to customize the stage1 success rate do it here. // Remember: skillup_modifier is (float). Lower is better switch(spec->tradeskill) { case FLETCHING: case ALCHEMY: case JEWELRY_MAKING: case POTTERY: skillup_modifier = 4; break; case BAKING: case BREWING: skillup_modifier = 3; break; case RESEARCH: skillup_modifier = 1; break; default: skillup_modifier = 2; break; } // Some tradeskills take the higher of one additional stat beside INT and WIS // to determine the skillup rate. Additionally these tradeskills do not have an // -15 modifier on their statbonus. if (spec->tradeskill == FLETCHING || spec->tradeskill == MAKE_POISON) { thirdstat = GetDEX(); stat_modifier = 0; } else if (spec->tradeskill == BLACKSMITHING) { thirdstat = GetSTR(); stat_modifier = 0; } int16 higher_from_int_wis = (GetINT() > GetWIS()) ? GetINT() : GetWIS(); int16 bonusstat = (higher_from_int_wis > thirdstat) ? higher_from_int_wis : thirdstat; vector< pair<uint32,uint8> >::iterator itr; //calculate the base success chance // For trivials over 68 the chance is (skill - 0.75*trivial) +51.5 // For trivial up to 68 the chance is (skill - trivial) + 66 if (spec->trivial >= 68) { chance = (user_skill - (0.75*spec->trivial)) + 51.5; } else { chance = (user_skill - spec->trivial) + 66; } int16 over_trivial = (int16)GetRawSkill(spec->tradeskill) - (int16)spec->trivial; //handle caps if(spec->nofail) { chance = 100; //cannot fail. _log(TRADESKILLS__TRACE, "...This combine cannot fail."); } else if(over_trivial >= 0) { // At reaching trivial the chance goes to 95% going up an additional // percent for every 40 skillpoints above the trivial. // The success rate is not modified through stats. // Mastery AAs are unaccounted for so far. // chance_AA = chance + ((100 - chance) * mastery_modifier) // But the 95% limit with an additional 1% for every 40 skill points // above critical still stands. // Mastery modifier is: 10%/25%/50% for rank one/two/three chance = 95.0f + (float(user_skill - spec->trivial) / 40.0f); Message_StringID(MT_Emote, TRADESKILL_TRIVIAL); } else if(chance < 5) { // Minimum chance is always 5 chance = 5; } else if(chance > 95) { //cap is 95, shouldent reach this before trivial, but just in case. chance = 95; } _log(TRADESKILLS__TRACE, "...Current skill: %d , Trivial: %d , Success chance: %f percent", user_skill , spec->trivial , chance); _log(TRADESKILLS__TRACE, "...Bonusstat: %d , INT: %d , WIS: %d , DEX: %d , STR: %d", bonusstat , GetINT() , GetWIS() , GetDEX() , GetSTR()); float res = MakeRandomFloat(0, 99); int AAChance = 0; //AA modifiers //can we do this with nested switches? if(spec->tradeskill == ALCHEMY){ switch(GetAA(aaAlchemyMastery)){ case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } if(spec->tradeskill == JEWELRY_MAKING){ switch(GetAA(aaJewelCraftMastery)){ case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } const Item_Struct* item = NULL; if (spec->tradeskill == BLACKSMITHING) { switch(GetAA(aaBlacksmithingMastery)) { case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } if (spec->tradeskill == BAKING) { switch(GetAA(aaBakingMastery)) { case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } if (spec->tradeskill == BREWING) { switch(GetAA(aaBrewingMastery)) { case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } if (spec->tradeskill == FLETCHING) { switch(GetAA(aaFletchingMastery2)) { case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } if (spec->tradeskill == POTTERY) { switch(GetAA(aaPotteryMastery)) { case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } if (spec->tradeskill == TAILORING) { switch(GetAA(aaTailoringMastery)) { case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } if (spec->tradeskill == RESEARCH) { switch(GetAA(aaArcaneTongues)) { case 1: AAChance = 10; break; case 2: AAChance = 25; break; case 3: AAChance = 50; break; } } if (((spec->tradeskill==75) || GetGM() || (chance > res)) || MakeRandomInt(0, 99) < AAChance){ success_modifier = 1; if(over_trivial < 0) CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill); Message_StringID(4,TRADESKILL_SUCCEED,spec->name.c_str()); _log(TRADESKILLS__TRACE, "Tradeskill success"); itr = spec->onsuccess.begin(); while(itr != spec->onsuccess.end() && !spec->quest) { //should we check this crap? SummonItem(itr->first, itr->second); item = database.GetItem(itr->first); if (this->GetGroup()) { entity_list.MessageGroup(this,true,MT_Skills,"%s has successfully fashioned %s!",GetName(),item->Name); } if(RuleB(TaskSystem, EnableTaskSystem)) UpdateTasksForItem(ActivityTradeSkill, itr->first, itr->second); itr++; } return(true); } else { success_modifier = 2; // Halves the chance if(over_trivial < 0) CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill); Message_StringID(MT_Emote,TRADESKILL_FAILED); _log(TRADESKILLS__TRACE, "Tradeskill failed"); if (this->GetGroup()) { entity_list.MessageGroup(this,true,MT_Skills,"%s was unsuccessful in %s tradeskill attempt.",GetName(),this->GetGender() == 0 ? "his" : this->GetGender() == 1 ? "her" : "its"); } itr = spec->onfail.begin(); while(itr != spec->onfail.end()) { //should we check these arguments? SummonItem(itr->first, itr->second); itr++; } // Rolls on each item, is possible to return everything int SalvageChance = aabonuses.SalvageChance + itembonuses.SalvageChance + spellbonuses.SalvageChance; // Skip check if not a normal TS or if a quest recipe these should be nofail, but check amyways if(SalvageChance && spec->tradeskill != 75 && !spec->quest) { itr = spec->salvage.begin(); uint8 sc = 0; while(itr != spec->salvage.end()) { for(sc = 0; sc < itr->second; sc++) if(MakeRandomInt(0,99) < SalvageChance) SummonItem(itr->first, 1); itr++; } } } return(false); }
//returns true on success bool Client::TradeskillExecute(DBTradeskillRecipe_Struct *spec) { if(spec == nullptr) return(false); uint16 user_skill = GetSkill(spec->tradeskill); float chance = 0.0; float skillup_modifier = 0.0; int16 thirdstat = 0; int16 stat_modifier = 15; uint16 success_modifier = 0; // Rework based on the info on eqtraders.com // http://mboards.eqtraders.com/eq/showthread.php?t=22246 // 09/10/2006 v0.1 (eq4me) // 09/11/2006 v0.2 (eq4me) // Todo: // Implementing AAs // Success modifiers based on recipes // Skillup modifiers based on the rarity of the ingredients // Some tradeskills are more eqal then others. ;-) // If you want to customize the stage1 success rate do it here. // Remember: skillup_modifier is (float). Lower is better switch(spec->tradeskill) { case SkillFletching: skillup_modifier = RuleI(Character, TradeskillUpFletching); break; case SkillAlchemy: skillup_modifier = RuleI(Character, TradeskillUpAlchemy); break; case SkillJewelryMaking: skillup_modifier = RuleI(Character, TradeskillUpJewelcrafting); break; case SkillPottery: skillup_modifier = RuleI(Character, TradeskillUpPottery); break; case SkillBaking: skillup_modifier = RuleI(Character, TradeskillUpBaking); break; case SkillBrewing: skillup_modifier = RuleI(Character, TradeskillUpBrewing); break; case SkillBlacksmithing: skillup_modifier = RuleI(Character, TradeskillUpBlacksmithing); break; case SkillResearch: skillup_modifier = RuleI(Character, TradeskillUpResearch); break; case SkillMakePoison: skillup_modifier = RuleI(Character, TradeskillUpMakePoison); break; case SkillTinkering: skillup_modifier = RuleI(Character, TradeskillUpTinkering); break; default: skillup_modifier = 2; break; } // Some tradeskills take the higher of one additional stat beside INT and WIS // to determine the skillup rate. Additionally these tradeskills do not have an // -15 modifier on their statbonus. if (spec->tradeskill == SkillFletching || spec->tradeskill == SkillMakePoison) { thirdstat = GetDEX(); stat_modifier = 0; } else if (spec->tradeskill == SkillBlacksmithing) { thirdstat = GetSTR(); stat_modifier = 0; } int16 higher_from_int_wis = (GetINT() > GetWIS()) ? GetINT() : GetWIS(); int16 bonusstat = (higher_from_int_wis > thirdstat) ? higher_from_int_wis : thirdstat; std::vector< std::pair<uint32,uint8> >::iterator itr; //calculate the base success chance // For trivials over 68 the chance is (skill - 0.75*trivial) +51.5 // For trivial up to 68 the chance is (skill - trivial) + 66 if (spec->trivial >= 68) { chance = (user_skill - (0.75*spec->trivial)) + 51.5; } else { chance = (user_skill - spec->trivial) + 66; } int16 over_trivial = (int16)GetRawSkill(spec->tradeskill) - (int16)spec->trivial; //handle caps if(spec->nofail) { chance = 100; //cannot fail. Log.Out(Logs::Detail, Logs::Tradeskills, "...This combine cannot fail."); } else if(over_trivial >= 0) { // At reaching trivial the chance goes to 95% going up an additional // percent for every 40 skillpoints above the trivial. // The success rate is not modified through stats. // Mastery AAs are unaccounted for so far. // chance_AA = chance + ((100 - chance) * mastery_modifier) // But the 95% limit with an additional 1% for every 40 skill points // above critical still stands. // Mastery modifier is: 10%/25%/50% for rank one/two/three chance = 95.0f + (float(user_skill - spec->trivial) / 40.0f); Message_StringID(MT_Emote, TRADESKILL_TRIVIAL); } else if(chance < 5) { // Minimum chance is always 5 chance = 5; } else if(chance > 95) { //cap is 95, shouldent reach this before trivial, but just in case. chance = 95; } Log.Out(Logs::Detail, Logs::Tradeskills, "...Current skill: %d , Trivial: %d , Success chance: %f percent", user_skill , spec->trivial , chance); Log.Out(Logs::Detail, Logs::Tradeskills, "...Bonusstat: %d , INT: %d , WIS: %d , DEX: %d , STR: %d", bonusstat , GetINT() , GetWIS() , GetDEX() , GetSTR()); float res = zone->random.Real(0, 99); int aa_chance = 0; aa_chance = spellbonuses.ReduceTradeskillFail[spec->tradeskill] + itembonuses.ReduceTradeskillFail[spec->tradeskill] + aabonuses.ReduceTradeskillFail[spec->tradeskill]; const Item_Struct* item = nullptr; chance = mod_tradeskill_chance(chance, spec); if (((spec->tradeskill==75) || GetGM() || (chance > res)) || zone->random.Roll(aa_chance)) { success_modifier = 1; if(over_trivial < 0) CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill); Message_StringID(4, TRADESKILL_SUCCEED, spec->name.c_str()); Log.Out(Logs::Detail, Logs::Tradeskills, "Tradeskill success"); itr = spec->onsuccess.begin(); while(itr != spec->onsuccess.end() && !spec->quest) { //should we check this crap? SummonItem(itr->first, itr->second); item = database.GetItem(itr->first); if (this->GetGroup()) { entity_list.MessageGroup(this, true, MT_Skills, "%s has successfully fashioned %s!", GetName(), item->Name); } /* QS: Player_Log_Trade_Skill_Events */ if (RuleB(QueryServ, PlayerLogTradeSkillEvents)){ std::string event_desc = StringFormat("Success :: fashioned recipe_id:%i tskillid:%i trivial:%i chance:%4.2f in zoneid:%i instid:%i", spec->recipe_id, spec->tradeskill, spec->trivial, chance, this->GetZoneID(), this->GetInstanceID()); QServ->PlayerLogEvent(Player_Log_Trade_Skill_Events, this->CharacterID(), event_desc); } if(RuleB(TaskSystem, EnableTaskSystem)) UpdateTasksForItem(ActivityTradeSkill, itr->first, itr->second); ++itr; } return(true); } /* Tradeskill Fail */ else { success_modifier = 2; // Halves the chance if(over_trivial < 0) CheckIncreaseTradeskill(bonusstat, stat_modifier, skillup_modifier, success_modifier, spec->tradeskill); Message_StringID(MT_Emote,TRADESKILL_FAILED); Log.Out(Logs::Detail, Logs::Tradeskills, "Tradeskill failed"); if (this->GetGroup()) { entity_list.MessageGroup(this,true,MT_Skills,"%s was unsuccessful in %s tradeskill attempt.",GetName(),this->GetGender() == 0 ? "his" : this->GetGender() == 1 ? "her" : "its"); } /* QS: Player_Log_Trade_Skill_Events */ if (RuleB(QueryServ, PlayerLogTradeSkillEvents)){ std::string event_desc = StringFormat("Failed :: recipe_id:%i tskillid:%i trivial:%i chance:%4.2f in zoneid:%i instid:%i", spec->recipe_id, spec->tradeskill, spec->trivial, chance, this->GetZoneID(), this->GetInstanceID()); QServ->PlayerLogEvent(Player_Log_Trade_Skill_Events, this->CharacterID(), event_desc); } itr = spec->onfail.begin(); while(itr != spec->onfail.end()) { //should we check these arguments? SummonItem(itr->first, itr->second); ++itr; } /* Salvage Item rolls */ // Rolls on each item, is possible to return everything int SalvageChance = aabonuses.SalvageChance + itembonuses.SalvageChance + spellbonuses.SalvageChance; // Skip check if not a normal TS or if a quest recipe these should be nofail, but check amyways if(SalvageChance && spec->tradeskill != 75 && !spec->quest) { itr = spec->salvage.begin(); uint8 sc = 0; while(itr != spec->salvage.end()) { for(sc = 0; sc < itr->second; sc++) if(zone->random.Roll(SalvageChance)) SummonItem(itr->first, 1); ++itr; } } } return(false); }