Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- I have been brainstorming ways to balance out our private server, in regards to the monster/npc experience.
- I decided to roll out an automatic experience system with a configurable formula, and also includes a very simple npc level system. I'll share it with the community.
- Note: If anyone can help me come up with a better formula, that would be very appreciated!
- ---------------------------------------------------------------------------------------------------------------------------------------
- - config.ini: Add the following lines:
- INCLUDE = ./config/npc_experience.ini
- INCLUDE_NOWARN = ./config_local/npc_experience.ini
- ---------------------------------------------------------------------------------------------------------------------------------------
- - Create a file named "config/npc_experience.ini":
- ## NPCNameSearchLevelPrefix / NPCNameSearchLevelSuffix (string)
- # Searches for the NPC level in the NPC name between NPCNameSearchLevelPrefix and NPCNameSearchLevelSuffix strings.
- # If NPCNameSearchLevelPrefix is empty. It will search from the beginning of the NPC name.
- # If NPCNameSearchLevelSuffix is empty. It will search until the end of the NPC name.
- # If both NPCNameSearchLevelPrefix and NPCNameSearchLevelSuffix are empty. It will not search for a level.
- # The level is stored as "npc->level" which can be referenced by code or via NPCExperienceAutoFormula "level" variable.
- # Example NPC Names: "Goat [Lvl 0]", "Piglet [Lvl 1]"
- # Note: If no level is found or searched in the name text, level 0 will be set.
- NPCNameSearchLevelPrefix = [Lvl
- NPCNameSearchLevelSuffix = ]
- ## NPCExperienceType (number)
- # Sets how NPC experience is calculated.
- # 0 = Manual experience, defined in pub enf file. This is the default behavior.
- # 1 = Automatic experience, defined by NPCExperienceAutoFormula.
- # 2 = Manual with fallback to automatic experience. If manual experience (in the pub enf file) is set to 0, it will fall back to automatic experience (defined by NPCExperienceAutoFormula).
- NPCExperienceType = 1
- ## NPCExperienceAutoFormula (number)
- # Formula for automatic experience calculation (when NPCExperienceType is set to 1 or 2).
- # Written in Reverse Polish notation: http://en.wikipedia.org/wiki/Reverse_Polish_notation
- # Infix / RPN online converter: http://www.mathblog.dk/tools/infix-postfix-converter/
- #
- # Example (experience specified by ENF pub file, with a bonus between -50% and 50% when character is between 10 levels of the NPC level):
- # char_level level - 10 min 10- max 0.05 * experience * experience + 0 max
- # Example (flat experience based on NPC stats):
- # 10 hp / mindam maxdam + + 2 accuracy evade + armor + / + 5 *
- # Example (scaling experience based on NPC stats, with a bonus between -50% and 50% when between 10 levels of the NPC level):
- # char_level level - 10 min 10- max 0.05 * 10 hp / mindam maxdam + + 2 accuracy evade + armor + / + 5 * * 10 hp / mindam maxdam + + 2 accuracy evade + armor + / + 5 * + 0 max
- NPCExperienceAutoFormula = char_level level - 10 min 10- max 0.05 * 10 hp / mindam maxdam + + 2 accuracy evade + armor + / + 5 * * 10 hp / mindam maxdam + + 2 accuracy evade + armor + / + 5 * + 0 max
- ## NPCExperienceAutoFormula NPC Variables:
- # accuracy
- # armor
- # direction
- # evade
- # experience
- # hp
- # level
- # mapid
- # maxdam
- # maxhp
- # mindam
- # npc
- # x
- # y
- ## NPCExperienceAutoFormula Character/Player Variables:
- # char_accuracy
- # char_admin
- # char_agi
- # char_armor
- # char_base_agi
- # char_base_cha
- # char_base_con
- # char_base_int
- # char_base_str
- # char_base_wis
- # char_bot
- # char_cha
- # char_class
- # char_con
- # char_direction
- # char_display_agi
- # char_display_cha
- # char_display_con
- # char_display_int
- # char_display_str
- # char_display_wis
- # char_evade
- # char_experience
- # char_gender
- # char_goldbank
- # char_haircolor
- # char_hairstyle
- # char_hidden
- # char_hp
- # char_int
- # char_karma
- # char_level
- # char_mapid
- # char_maxdam
- # char_maxhp
- # char_maxsp
- # char_maxtp
- # char_maxweight
- # char_mindam
- # char_race
- # char_rebirth
- # char_sitting
- # char_skillpoints
- # char_statpoints
- # char_str
- # char_tp
- # char_usage
- # char_weight
- # char_whispers
- # char_wis
- # char_x
- # char_y
- ---------------------------------------------------------------------------------------------------------------------------------------
- - In src\eoserv_config.cpp, inside the following method:
- void eoserv_config_validate_config(Config& config)
- Add:
- eoserv_config_default(config, "NPCNameSearchLevelPrefix", "");
- eoserv_config_default(config, "NPCNameSearchLevelSuffix", "");
- eoserv_config_default(config, "NPCExperienceType", 0);
- eoserv_config_default(config, "NPCExperienceAutoFormula", "");
- ---------------------------------------------------------------------------------------------------------------------------------------
- - In src\npc.cpp, inside the following method:
- NPC::NPC(Map *map, short id, unsigned char x, unsigned char y, unsigned char spawn_type, short spawn_time, unsigned char index, bool temporary)
- Add:
- std::string levelPrefixString = static_cast<std::string>(this->map->world->config["NPCNameSearchLevelPrefix"]);
- std::string levelSuffixString = static_cast<std::string>(this->map->world->config["NPCNameSearchLevelSuffix"]);
- if (levelPrefixString.length() || levelSuffixString.length()) {
- std::string levelString = util::string_between_strings(this->ENF().name, levelPrefixString, levelSuffixString);
- this->level = util::to_int(levelString);
- } else {
- this->level = 0;
- }
- ---------------------------------------------------------------------------------------------------------------------------------------
- - Inside src\npc.cpp, add the following method:
- unsigned short NPC::Experience(Character *character) {
- unsigned short experience = 0;
- int experienceType = this->map->world->config["NPCExperienceType"];
- // 0 = Manual experience, defined in pub enf file. This is the default behavior.
- // 1 = Automatic experience, defined by NPCExperienceAutoFormula.
- // 2 = Manual with fallback to automatic experience. If manual experience (in the pub enf file) is set to 0, it will fall back to automatic experience (defined by NPCExperienceAutoFormula).
- if (experienceType == 1 || experienceType == 2) { //Automatic experience || Manual with fallback to automatic experience
- if (experienceType == 2 && this->ENF().exp > 0) { //Manual experience
- experience = this->ENF().exp;
- } else { //Automatic experience
- std::unordered_map<std::string, double> formula_vars;
- this->FormulaVars(formula_vars);
- character->FormulaVars(formula_vars, "char_");
- formula_vars["hp"] = formula_vars["maxhp"];
- experience = std::round(rpn_eval(rpn_parse(this->map->world->config["NPCExperienceAutoFormula"]), formula_vars));
- }
- } else { //Manual experience
- experience = this->ENF().exp;
- }
- return experience;
- }
- ---------------------------------------------------------------------------------------------------------------------------------------
- - In src\npc.cpp, modify the following method:
- void NPC::Killed(Character *from, int amount, int spell_id)
- Change all references of "this->ENF().exp" to "this->Experience(character);".
- - It should look like this:
- double droprate = this->map->world->config["DropRate"];
- double exprate = this->map->world->config["ExpRate"];
- int sharemode = this->map->world->config["ShareMode"];
- int partysharemode = this->map->world->config["PartyShareMode"];
- int dropratemode = this->map->world->config["DropRateMode"];
- std::set<Party *> parties;
- int most_damage_counter = 0;
- Character *most_damage = nullptr;
- NPC_Drop *drop = nullptr;
- this->alive = false;
- this->dead_since = int(Timer::GetTime());
- if (dropratemode == 1)
- {
- std::vector<NPC_Drop *> drops;
- UTIL_FOREACH_CREF(this->Data().drops, checkdrop)
- {
- if (util::rand(0.0, 100.0) <= checkdrop->chance * droprate)
- {
- drops.push_back(checkdrop.get());
- }
- }
- if (drops.size() > 0)
- {
- drop = drops[util::rand(0, drops.size()-1)];
- }
- }
- else if (dropratemode == 2)
- {
- UTIL_FOREACH_CREF(this->Data().drops, checkdrop)
- {
- if (util::rand(0.0, 100.0) <= checkdrop->chance * droprate)
- {
- drop = checkdrop.get();
- break;
- }
- }
- }
- else if (dropratemode == 3)
- {
- double roll = util::rand(0.0, this->Data().drops_chance_total);
- UTIL_FOREACH_CREF(this->Data().drops, checkdrop)
- {
- if (roll >= checkdrop->chance_offset && roll < checkdrop->chance_offset+checkdrop->chance)
- {
- drop = checkdrop.get();
- break;
- }
- }
- }
- if (sharemode == 1)
- {
- UTIL_FOREACH_CREF(this->damagelist, opponent)
- {
- if (opponent->damage > most_damage_counter)
- {
- most_damage_counter = opponent->damage;
- most_damage = opponent->attacker;
- }
- }
- }
- int dropuid = 0;
- int dropid = 0;
- int dropamount = 0;
- Character* drop_winner = nullptr;
- if (drop)
- {
- dropid = drop->id;
- dropamount = std::min<int>(util::rand(drop->min, drop->max), this->map->world->config["MaxItem"]);
- if (dropid <= 0 || static_cast<std::size_t>(dropid) >= this->map->world->eif->data.size() || dropamount <= 0)
- goto abort_drop;
- dropuid = this->map->GenerateItemID();
- std::shared_ptr<Map_Item> newitem(std::make_shared<Map_Item>(dropuid, dropid, dropamount, this->x, this->y, from->PlayerID(), Timer::GetTime() + static_cast<int>(this->map->world->config["ProtectNPCDrop"])));
- this->map->items.push_back(newitem);
- // Selects a random number between 0 and maxhp, and decides the winner based on that
- switch (sharemode)
- {
- case 0:
- drop_winner = from;
- break;
- case 1:
- drop_winner = most_damage;
- break;
- case 2:
- {
- int rewarded_hp = util::rand(0, this->totaldamage - 1);
- int count_hp = 0;
- UTIL_FOREACH_CREF(this->damagelist, opponent)
- {
- if (opponent->attacker->InRange(this))
- {
- if (rewarded_hp >= count_hp && rewarded_hp < opponent->damage)
- {
- drop_winner = opponent->attacker;
- break;
- }
- count_hp += opponent->damage;
- }
- }
- }
- break;
- case 3:
- {
- int rand = util::rand(0, this->damagelist.size() - 1);
- int i = 0;
- UTIL_FOREACH_CREF(this->damagelist, opponent)
- {
- if (opponent->attacker->InRange(this))
- {
- if (rand == i++)
- {
- drop_winner = opponent->attacker;
- break;
- }
- }
- }
- }
- break;
- }
- }
- abort_drop:
- if (drop_winner)
- this->map->items.back()->owner = drop_winner->PlayerID();
- UTIL_FOREACH(this->map->characters, character)
- {
- std::list<std::unique_ptr<NPC_Opponent>>::iterator findopp = this->damagelist.begin();
- for (; findopp != this->damagelist.end() && (*findopp)->attacker != character; ++findopp); // no loop body
- if (findopp != this->damagelist.end() || character->InRange(this))
- {
- bool level_up = false;
- PacketBuilder builder(spell_id == -1 ? PACKET_NPC : PACKET_CAST, PACKET_SPEC, 26);
- unsigned short experience = this->Experience(character);
- if (experience != 0)
- {
- if (findopp != this->damagelist.end())
- {
- int reward;
- switch (sharemode)
- {
- case 0:
- if (character == from)
- {
- reward = int(std::ceil(double(experience) * exprate));
- if (reward > 0)
- {
- if (partysharemode)
- {
- if (character->party)
- {
- character->party->ShareEXP(reward, partysharemode, this->map);
- }
- else
- {
- character->exp += reward;
- }
- }
- else
- {
- character->exp += reward;
- }
- }
- }
- break;
- case 1:
- if (character == most_damage)
- {
- reward = int(std::ceil(double(experience) * exprate));
- if (reward > 0)
- {
- if (partysharemode)
- {
- if (character->party)
- {
- character->party->ShareEXP(reward, partysharemode, this->map);
- }
- else
- {
- character->exp += reward;
- }
- }
- else
- {
- character->exp += reward;
- }
- }
- }
- break;
- case 2:
- reward = int(std::ceil(double(experience) * exprate * (double((*findopp)->damage) / double(this->totaldamage))));
- if (reward > 0)
- {
- if (partysharemode)
- {
- if (character->party)
- {
- character->party->temp_expsum += reward;
- parties.insert(character->party);
- }
- else
- {
- character->exp += reward;
- }
- }
- else
- {
- character->exp += reward;
- }
- }
- break;
- case 3:
- reward = int(std::ceil(double(experience) * exprate * (double(this->damagelist.size()) / 1.0)));
- ...
- ---------------------------------------------------------------------------------------------------------------------------------------
- - In src\npc.cpp, inside the following method:
- void NPC::FormulaVars(std::unordered_map<std::string, double> &vars, std::string prefix)
- - Add the following code:
- vv(data.exp, "experience") v(level)
- ---------------------------------------------------------------------------------------------------------------------------------------
- - Inside src\npc.hpp, in "class NPC". Add the following:
- short level;
- unsigned short Experience(Character *character);
- ---------------------------------------------------------------------------------------------------------------------------------------
- If you have a rebirth system, follow this step:
- - In src\character.cpp, inside the following method:
- void Character::FormulaVars(std::unordered_map<std::string, double> &vars, std::string prefix)
- - Add the following code:
- v(rebirth)
- ---------------------------------------------------------------------------------------------------------------------------------------
- - In src\util.cpp, add the following method:
- std::string string_between_strings(std::string originalString, std::string startString, std::string endString, bool caseSensitive)
- {
- if (!caseSensitive) {
- originalString = util::lowercase(originalString);
- startString = util::lowercase(startString);
- endString = util::lowercase(endString);
- }
- int start = startString.length() > 0 ? originalString.find(startString) : 0;
- if (start >= 0) {
- std::string tstr = originalString.substr(start + startString.length());
- int stop = endString.length() > 0 ? tstr.find(endString) : tstr.length();
- if (stop > 1) {
- return originalString.substr(start + startString.length(), stop);
- }
- }
- return "";
- }
- ---------------------------------------------------------------------------------------------------------------------------------------
- In src\util.hpp, add the declaration for the method:
- std::string string_between_strings(std::string originalString, std::string startString, std::string endString, bool caseSensitive = false);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement