Advertisement
Guest User

EOServ Automatic NPC Experience and Level System

a guest
Mar 6th, 2017
230
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 18.17 KB | None | 0 0
  1. I have been brainstorming ways to balance out our private server, in regards to the monster/npc experience.
  2.  
  3. 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.
  4.  
  5. Note: If anyone can help me come up with a better formula, that would be very appreciated!
  6.  
  7. ---------------------------------------------------------------------------------------------------------------------------------------
  8.  
  9. - config.ini: Add the following lines:
  10.  
  11. INCLUDE = ./config/npc_experience.ini
  12. INCLUDE_NOWARN = ./config_local/npc_experience.ini
  13.  
  14. ---------------------------------------------------------------------------------------------------------------------------------------
  15.  
  16. - Create a file named "config/npc_experience.ini":
  17.  
  18. ## NPCNameSearchLevelPrefix / NPCNameSearchLevelSuffix (string)
  19. # Searches for the NPC level in the NPC name between NPCNameSearchLevelPrefix and NPCNameSearchLevelSuffix strings.
  20. # If NPCNameSearchLevelPrefix is empty. It will search from the beginning of the NPC name.
  21. # If NPCNameSearchLevelSuffix is empty. It will search until the end of the NPC name.
  22. # If both NPCNameSearchLevelPrefix and NPCNameSearchLevelSuffix are empty. It will not search for a level.
  23. # The level is stored as "npc->level" which can be referenced by code or via NPCExperienceAutoFormula "level" variable.
  24. # Example NPC Names: "Goat [Lvl 0]", "Piglet [Lvl 1]"
  25. # Note: If no level is found or searched in the name text, level 0 will be set.
  26. NPCNameSearchLevelPrefix = [Lvl
  27. NPCNameSearchLevelSuffix = ]
  28.  
  29. ## NPCExperienceType (number)
  30. # Sets how NPC experience is calculated.
  31. # 0 = Manual experience, defined in pub enf file. This is the default behavior.
  32. # 1 = Automatic experience, defined by NPCExperienceAutoFormula.
  33. # 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).
  34. NPCExperienceType = 1
  35.  
  36. ## NPCExperienceAutoFormula (number)
  37. # Formula for automatic experience calculation (when NPCExperienceType is set to 1 or 2).
  38. # Written in Reverse Polish notation: http://en.wikipedia.org/wiki/Reverse_Polish_notation
  39. # Infix / RPN online converter: http://www.mathblog.dk/tools/infix-postfix-converter/
  40. #
  41. # Example (experience specified by ENF pub file, with a bonus between -50% and 50% when character is between 10 levels of the NPC level):
  42. #   char_level level - 10 min 10- max 0.05 * experience * experience +   0 max
  43. # Example (flat experience based on NPC stats):
  44. #   10 hp / mindam maxdam + + 2 accuracy evade + armor + / + 5 *
  45. # Example (scaling experience based on NPC stats, with a bonus between -50% and 50% when between 10 levels of the NPC level):
  46. #   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
  47. 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
  48.  
  49. ## NPCExperienceAutoFormula NPC Variables:
  50. # accuracy
  51. # armor
  52. # direction
  53. # evade
  54. # experience
  55. # hp
  56. # level
  57. # mapid
  58. # maxdam
  59. # maxhp
  60. # mindam
  61. # npc
  62. # x
  63. # y
  64.  
  65. ## NPCExperienceAutoFormula Character/Player Variables:
  66. # char_accuracy
  67. # char_admin
  68. # char_agi
  69. # char_armor
  70. # char_base_agi
  71. # char_base_cha
  72. # char_base_con
  73. # char_base_int
  74. # char_base_str
  75. # char_base_wis
  76. # char_bot
  77. # char_cha
  78. # char_class
  79. # char_con
  80. # char_direction
  81. # char_display_agi
  82. # char_display_cha
  83. # char_display_con
  84. # char_display_int
  85. # char_display_str
  86. # char_display_wis
  87. # char_evade
  88. # char_experience
  89. # char_gender
  90. # char_goldbank
  91. # char_haircolor
  92. # char_hairstyle
  93. # char_hidden
  94. # char_hp
  95. # char_int
  96. # char_karma
  97. # char_level
  98. # char_mapid
  99. # char_maxdam
  100. # char_maxhp
  101. # char_maxsp
  102. # char_maxtp
  103. # char_maxweight
  104. # char_mindam
  105. # char_race
  106. # char_rebirth
  107. # char_sitting
  108. # char_skillpoints
  109. # char_statpoints
  110. # char_str
  111. # char_tp
  112. # char_usage
  113. # char_weight
  114. # char_whispers
  115. # char_wis
  116. # char_x
  117. # char_y
  118.  
  119. ---------------------------------------------------------------------------------------------------------------------------------------
  120.  
  121. - In src\eoserv_config.cpp, inside the following method:
  122. void eoserv_config_validate_config(Config& config)
  123.  
  124. Add:
  125.    eoserv_config_default(config, "NPCNameSearchLevelPrefix", "");
  126.    eoserv_config_default(config, "NPCNameSearchLevelSuffix", "");
  127.    eoserv_config_default(config, "NPCExperienceType", 0);
  128.    eoserv_config_default(config, "NPCExperienceAutoFormula", "");
  129.  
  130. ---------------------------------------------------------------------------------------------------------------------------------------
  131.  
  132. - In src\npc.cpp, inside the following method:
  133.  
  134. NPC::NPC(Map *map, short id, unsigned char x, unsigned char y, unsigned char spawn_type, short spawn_time, unsigned char index, bool temporary)
  135. Add:
  136.    std::string levelPrefixString = static_cast<std::string>(this->map->world->config["NPCNameSearchLevelPrefix"]);
  137.    std::string levelSuffixString = static_cast<std::string>(this->map->world->config["NPCNameSearchLevelSuffix"]);
  138.    if (levelPrefixString.length() || levelSuffixString.length()) {
  139.        std::string levelString = util::string_between_strings(this->ENF().name, levelPrefixString, levelSuffixString);
  140.        this->level = util::to_int(levelString);
  141.    } else {
  142.        this->level = 0;
  143.    }
  144.  
  145. ---------------------------------------------------------------------------------------------------------------------------------------
  146.  
  147. - Inside src\npc.cpp, add the following method:
  148.  
  149. unsigned short NPC::Experience(Character *character) {
  150.    unsigned short experience = 0;
  151.    int experienceType = this->map->world->config["NPCExperienceType"];
  152.    // 0 = Manual experience, defined in pub enf file. This is the default behavior.
  153.    // 1 = Automatic experience, defined by NPCExperienceAutoFormula.
  154.    // 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).
  155.  
  156.    if (experienceType == 1 || experienceType == 2) { //Automatic experience || Manual with fallback to automatic experience
  157.        if (experienceType == 2 && this->ENF().exp > 0) { //Manual experience
  158.            experience = this->ENF().exp;
  159.        } else { //Automatic experience
  160.            std::unordered_map<std::string, double> formula_vars;
  161.            this->FormulaVars(formula_vars);
  162.            character->FormulaVars(formula_vars, "char_");
  163.            formula_vars["hp"] = formula_vars["maxhp"];
  164.  
  165.            experience = std::round(rpn_eval(rpn_parse(this->map->world->config["NPCExperienceAutoFormula"]), formula_vars));
  166.        }
  167.    } else { //Manual experience
  168.        experience = this->ENF().exp;
  169.    }
  170.    return experience;
  171. }
  172.  
  173. ---------------------------------------------------------------------------------------------------------------------------------------
  174.  
  175. - In src\npc.cpp, modify the following method:
  176.  
  177. void NPC::Killed(Character *from, int amount, int spell_id)
  178. Change all references of "this->ENF().exp" to "this->Experience(character);".
  179.  
  180. - It should look like this:
  181.  
  182.    double droprate = this->map->world->config["DropRate"];
  183.    double exprate = this->map->world->config["ExpRate"];
  184.    int sharemode = this->map->world->config["ShareMode"];
  185.    int partysharemode = this->map->world->config["PartyShareMode"];
  186.    int dropratemode = this->map->world->config["DropRateMode"];
  187.    std::set<Party *> parties;
  188.  
  189.    int most_damage_counter = 0;
  190.    Character *most_damage = nullptr;
  191.    NPC_Drop *drop = nullptr;
  192.  
  193.    this->alive = false;
  194.  
  195.    this->dead_since = int(Timer::GetTime());
  196.  
  197.    if (dropratemode == 1)
  198.    {
  199.        std::vector<NPC_Drop *> drops;
  200.  
  201.        UTIL_FOREACH_CREF(this->Data().drops, checkdrop)
  202.        {
  203.            if (util::rand(0.0, 100.0) <= checkdrop->chance * droprate)
  204.            {
  205.                drops.push_back(checkdrop.get());
  206.            }
  207.        }
  208.  
  209.        if (drops.size() > 0)
  210.        {
  211.            drop = drops[util::rand(0, drops.size()-1)];
  212.        }
  213.    }
  214.    else if (dropratemode == 2)
  215.    {
  216.        UTIL_FOREACH_CREF(this->Data().drops, checkdrop)
  217.        {
  218.            if (util::rand(0.0, 100.0) <= checkdrop->chance * droprate)
  219.            {
  220.                drop = checkdrop.get();
  221.                break;
  222.            }
  223.        }
  224.    }
  225.    else if (dropratemode == 3)
  226.    {
  227.        double roll = util::rand(0.0, this->Data().drops_chance_total);
  228.  
  229.        UTIL_FOREACH_CREF(this->Data().drops, checkdrop)
  230.        {
  231.            if (roll >= checkdrop->chance_offset && roll < checkdrop->chance_offset+checkdrop->chance)
  232.            {
  233.                drop = checkdrop.get();
  234.                break;
  235.            }
  236.        }
  237.    }
  238.  
  239.    if (sharemode == 1)
  240.    {
  241.        UTIL_FOREACH_CREF(this->damagelist, opponent)
  242.        {
  243.            if (opponent->damage > most_damage_counter)
  244.            {
  245.                most_damage_counter = opponent->damage;
  246.                most_damage = opponent->attacker;
  247.            }
  248.        }
  249.    }
  250.  
  251.    int dropuid = 0;
  252.    int dropid = 0;
  253.    int dropamount = 0;
  254.    Character* drop_winner = nullptr;
  255.  
  256.    if (drop)
  257.    {
  258.        dropid = drop->id;
  259.        dropamount = std::min<int>(util::rand(drop->min, drop->max), this->map->world->config["MaxItem"]);
  260.  
  261.        if (dropid <= 0 || static_cast<std::size_t>(dropid) >= this->map->world->eif->data.size() || dropamount <= 0)
  262.            goto abort_drop;
  263.  
  264.        dropuid = this->map->GenerateItemID();
  265.  
  266.        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"])));
  267.        this->map->items.push_back(newitem);
  268.  
  269.        // Selects a random number between 0 and maxhp, and decides the winner based on that
  270.        switch (sharemode)
  271.        {
  272.            case 0:
  273.                drop_winner = from;
  274.                break;
  275.  
  276.            case 1:
  277.                drop_winner = most_damage;
  278.                break;
  279.  
  280.            case 2:
  281.            {
  282.                int rewarded_hp = util::rand(0, this->totaldamage - 1);
  283.                int count_hp = 0;
  284.                UTIL_FOREACH_CREF(this->damagelist, opponent)
  285.                {
  286.                    if (opponent->attacker->InRange(this))
  287.                    {
  288.                        if (rewarded_hp >= count_hp && rewarded_hp < opponent->damage)
  289.                        {
  290.                            drop_winner = opponent->attacker;
  291.                            break;
  292.                        }
  293.  
  294.                        count_hp += opponent->damage;
  295.                    }
  296.                }
  297.            }
  298.                break;
  299.  
  300.            case 3:
  301.            {
  302.                int rand = util::rand(0, this->damagelist.size() - 1);
  303.                int i = 0;
  304.                UTIL_FOREACH_CREF(this->damagelist, opponent)
  305.                {
  306.                    if (opponent->attacker->InRange(this))
  307.                    {
  308.                        if (rand == i++)
  309.                        {
  310.                            drop_winner = opponent->attacker;
  311.                            break;
  312.                        }
  313.                    }
  314.                }
  315.            }
  316.                break;
  317.        }
  318.    }
  319.    abort_drop:
  320.  
  321.    if (drop_winner)
  322.        this->map->items.back()->owner = drop_winner->PlayerID();
  323.  
  324.    UTIL_FOREACH(this->map->characters, character)
  325.    {
  326.        std::list<std::unique_ptr<NPC_Opponent>>::iterator findopp = this->damagelist.begin();
  327.        for (; findopp != this->damagelist.end() && (*findopp)->attacker != character; ++findopp); // no loop body
  328.  
  329.        if (findopp != this->damagelist.end() || character->InRange(this))
  330.        {
  331.            bool level_up = false;
  332.  
  333.            PacketBuilder builder(spell_id == -1 ? PACKET_NPC : PACKET_CAST, PACKET_SPEC, 26);
  334.  
  335.            unsigned short experience = this->Experience(character);
  336.            if (experience != 0)
  337.            {
  338.                if (findopp != this->damagelist.end())
  339.                {
  340.                    int reward;
  341.                    switch (sharemode)
  342.                    {
  343.                        case 0:
  344.                            if (character == from)
  345.                            {
  346.                                reward = int(std::ceil(double(experience) * exprate));
  347.  
  348.                                if (reward > 0)
  349.                                {
  350.                                    if (partysharemode)
  351.                                    {
  352.                                        if (character->party)
  353.                                        {
  354.                                            character->party->ShareEXP(reward, partysharemode, this->map);
  355.                                        }
  356.                                        else
  357.                                        {
  358.                                            character->exp += reward;
  359.                                        }
  360.                                    }
  361.                                    else
  362.                                    {
  363.                                        character->exp += reward;
  364.                                    }
  365.                                }
  366.                            }
  367.                            break;
  368.  
  369.                        case 1:
  370.                            if (character == most_damage)
  371.                            {
  372.                                reward = int(std::ceil(double(experience) * exprate));
  373.  
  374.                                if (reward > 0)
  375.                                {
  376.                                    if (partysharemode)
  377.                                    {
  378.                                        if (character->party)
  379.                                        {
  380.                                            character->party->ShareEXP(reward, partysharemode, this->map);
  381.                                        }
  382.                                        else
  383.                                        {
  384.                                            character->exp += reward;
  385.                                        }
  386.                                    }
  387.                                    else
  388.                                    {
  389.                                        character->exp += reward;
  390.                                    }
  391.                                }
  392.                            }
  393.                            break;
  394.  
  395.                        case 2:
  396.                            reward = int(std::ceil(double(experience) * exprate * (double((*findopp)->damage) / double(this->totaldamage))));
  397.  
  398.                            if (reward > 0)
  399.                            {
  400.                                if (partysharemode)
  401.                                {
  402.                                    if (character->party)
  403.                                    {
  404.                                        character->party->temp_expsum += reward;
  405.                                        parties.insert(character->party);
  406.                                    }
  407.                                    else
  408.                                    {
  409.                                        character->exp += reward;
  410.                                    }
  411.                                }
  412.                                else
  413.                                {
  414.                                    character->exp += reward;
  415.                                }
  416.                            }
  417.                            break;
  418.  
  419.                        case 3:
  420.                            reward = int(std::ceil(double(experience) * exprate * (double(this->damagelist.size()) / 1.0)));
  421.                 ...
  422.  
  423. ---------------------------------------------------------------------------------------------------------------------------------------
  424.  
  425. - In src\npc.cpp, inside the following method:
  426.  
  427. void NPC::FormulaVars(std::unordered_map<std::string, double> &vars, std::string prefix)
  428.  
  429. - Add the following code:
  430.    vv(data.exp, "experience") v(level)
  431.  
  432. ---------------------------------------------------------------------------------------------------------------------------------------
  433.  
  434. - Inside src\npc.hpp, in "class NPC". Add the following:
  435.        short level;
  436.        unsigned short Experience(Character *character);
  437.  
  438. ---------------------------------------------------------------------------------------------------------------------------------------
  439.  
  440. If you have a rebirth system, follow this step:
  441.  
  442. - In src\character.cpp, inside the following method:
  443.  
  444. void Character::FormulaVars(std::unordered_map<std::string, double> &vars, std::string prefix)
  445.  
  446. - Add the following code:
  447.  
  448.     v(rebirth)
  449.  
  450. ---------------------------------------------------------------------------------------------------------------------------------------
  451.  
  452. - In src\util.cpp, add the following method:
  453.  
  454. std::string string_between_strings(std::string originalString, std::string startString, std::string endString, bool caseSensitive)
  455. {
  456.    if (!caseSensitive) {
  457.        originalString = util::lowercase(originalString);
  458.        startString = util::lowercase(startString);
  459.        endString = util::lowercase(endString);
  460.    }
  461.  
  462.    int start = startString.length() > 0 ? originalString.find(startString) : 0;
  463.    if (start >= 0) {
  464.      std::string tstr = originalString.substr(start + startString.length());
  465.      int stop = endString.length() > 0 ? tstr.find(endString) : tstr.length();
  466.      if (stop > 1) {
  467.        return originalString.substr(start + startString.length(), stop);
  468.      }
  469.    }
  470.    return "";
  471. }
  472.  
  473. ---------------------------------------------------------------------------------------------------------------------------------------
  474.  
  475. In src\util.hpp, add the declaration for the method:
  476.  
  477. 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