Jump to content

[C++] Addon Apply Time Testing


Recommended Posts

  • Premium
This is the hidden content, please

I've made a simple program to test how much time and switchers a normal player would spend to find a certain value of the average damage bonus. It is possible to choose how many times you want the test to be done and, at the end, it will display how many times the bonus was over the values 30/40/50 and the average time spent to find the value desired.

Pressing ESC it's possible to exit and see the results before it finds a bonus during a try (WINDOWS ONLY, but I'll probably make a Linux/OS X/BSD version).

There's a define called METIN_ORIGINAL, when commented, it uses the standard library random generators, otherwise it follows the same logic used in the original sources.

Example 5 tries, average bonus of value 50:

4kIENGx.png

Raw source code if you wanna use it without the ESC feature in other OSes (needs at least C++17 and the fmt library):

Spoiler
#define FMT_HEADER_ONLY
#define WIN32_LEAN_AND_MEAN
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(WIN64)
#include <windows.h>
#include <winuser.h>
#endif
#include "fmt/core.h"
#include "fmt/ranges.h"
#include <random>
#include <algorithm>
#include <iostream>
#include <thread>
#include <functional>
#include <chrono>
#include <numeric>
using namespace std::chrono_literals;
#define METIN_ORIGINAL
#ifndef METIN_ORIGINAL
double gauss_random(float avg, float sigma)
{
	static std::random_device rd{};
	static std::mt19937 gen{ rd() };
	std::normal_distribution<> nd(avg, sigma);

	return std::round(nd(gen));
}
#else
DWORD thecore_random()
{
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(WIN64)
    return rand();
#else
    return random();
#endif
}
double uniform_random(double a, double b)
{
    return thecore_random() / (RAND_MAX + 1.f) * (b - a) + a;
}
float gauss_random(float avg, float sigma)
{
    static bool haveNextGaussian = false;
    static float nextGaussian = 0.0f;

    if (haveNextGaussian)
    {
        haveNextGaussian = false;
        return nextGaussian * sigma + avg;
    }
    else
    {
        double v1, v2, s;
        do {
            //v1 = 2 * nextDouble() - 1;   // between -1.0 and 1.0
            //v2 = 2 * nextDouble() - 1;   // between -1.0 and 1.0
            v1 = uniform_random(-1.f, 1.f);
            v2 = uniform_random(-1.f, 1.f);
            s = v1 * v1 + v2 * v2;
        } while (s >= 1.f || fabs(s) < FLT_EPSILON);
        double multiplier = sqrtf(-2 * logf(s) / s);
        nextGaussian = v2 * multiplier;
        haveNextGaussian = true;
        return v1 * multiplier * sigma + avg;
    }
}
#endif
int number(int from, int to)
{
    if (from > to)
    {
        int tmp = from;

        fmt::print("number(): first argument is bigger than second argument {} -> {}", from, to);

        from = to;
        to = tmp;
    }

#ifndef METIN_ORIGINAL
    static std::random_device rd;
    static std::mt19937 gen(rd());
    std::uniform_int_distribution<int> distribution(from, to);
    return distribution(gen);
#else
    int returnValue = 0;
    if ((to - from + 1) != 0)
        returnValue = ((thecore_random() % (to - from + 1)) + from);
    else
        fmt::print("number(): devided by 0");

    return returnValue;
#endif
}

int MINMAX(int min, int value, int max)
{
    int tv;

    tv = (min > value ? min : value);
    return (max < tv) ? max : tv;
}

auto findApplyAddons()
{
    int iSkillBonus = MINMAX(-30, (int)(gauss_random(0, 5) + 0.5f), 30);
    int iNormalHitBonus = 0;
    if (abs(iSkillBonus) <= 20)
        iNormalHitBonus = -2 * iSkillBonus + abs(number(-8, 8) + number(-8, 8)) + number(1, 4);
    else
        iNormalHitBonus = -2 * iSkillBonus + number(1, 5);

    struct result { int normalHitBonus; int skillBonus; };
    return result{ iNormalHitBonus, iSkillBonus };
}

std::string secondsToDHMS(int seconds)
{
    int day = seconds / (24 * 3600);

    seconds = seconds % (24 * 3600);
    int hour = seconds / 3600;

    seconds %= 3600;
    int minutes = seconds / 60;

    seconds %= 60;
    int sec = seconds;

    return fmt::format("Days: {} - Hours: {} - Minutes: {} - Seconds: {}", day, hour, minutes, sec);
}

int main()
{
#ifdef METIN_ORIGINAL
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(WIN64)
    srand(time(0));
#endif
#endif

    int average_bonus = 0;
    int skill_bonus = 0;
    double switcherSpeed = 0.0;
    int tries = 1;
    std::vector<int>averagesVec{};
    std::vector<int>over30{};
    std::vector<int>over40{};
    std::vector<int>over50{};
    std::vector<int>max{};
    std::vector<double>seconds;
    std::vector<int>switchers{};

    fmt::print("Type your switchbot delay in seconds (usually 0.08 by default): \n");
    std::cin >> switcherSpeed;
    fmt::print("Type the average bonus desired: \n");
    std::cin >> average_bonus;
    fmt::print("Type the skill bonus desired (0 to ignore it): \n");
    std::cin >> skill_bonus;
    fmt::print("Type the number of times you want to find these bonuses: \n");
    std::cin >> tries;
    if (tries <= 0) {
        tries = 1;
    }

    fmt::print("You can pause the program pressing ESC\n");
    std::this_thread::sleep_for(1s);
    for (int i = 0; i < tries; i++)
    {
        averagesVec.clear();
        while (true) {
            auto bonuses = findApplyAddons();
            averagesVec.emplace_back(bonuses.normalHitBonus);
            fmt::print("AVERAGE BONUS: {} - SKILL BONUS: {} - SWITCHER NUMBER: {}\n", bonuses.normalHitBonus, bonuses.skillBonus, averagesVec.size());

#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(WIN64)
            if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
                break;
            }
#endif
            if (bonuses.normalHitBonus >= average_bonus && (skill_bonus == 0 || bonuses.skillBonus >= skill_bonus)) {
                fmt::print("found average bonus >= {} ({})with {} skill bonus with {} switchers\n", average_bonus, bonuses.normalHitBonus, bonuses.skillBonus, averagesVec.size());
                break;
            }
        }
        seconds.emplace_back(averagesVec.size() * switcherSpeed);
        auto _over30 = std::count_if(averagesVec.begin(), averagesVec.end(), [](int val) {return val >= 30; });
        auto _over40 = std::count_if(averagesVec.begin(), averagesVec.end(), [](int val) {return val >= 40; });
        auto _over50 = std::count_if(averagesVec.begin(), averagesVec.end(), [](int val) {return val >= 50; });
        auto _max = *std::max_element(averagesVec.begin(), averagesVec.end());
        over30.emplace_back(_over30);
        over40.emplace_back(_over40);
        over50.emplace_back(_over50);
        max.emplace_back(_max);
        switchers.emplace_back(averagesVec.size());
    }
    fmt::print("Over30: \n{}\n", over30);
    fmt::print("Over40: \n{}\n", over40);
    fmt::print("Over50: \n{}\n", over50);
    fmt::print("Max: \n{}\n", max);
    fmt::print("Seconds: \n{}\n", seconds);
    fmt::print("[\n");
    for (const auto& sec : seconds) {
        fmt::print("{},\n", secondsToDHMS(sec));
    }
    fmt::print("]\n");
    fmt::print("Switchers used: \n{}\n", switchers);

    auto avgTime = std::accumulate(seconds.begin(), seconds.end(), 0.0, [&](double a, double b) { return  a + static_cast<double>(b) / seconds.size(); });
    auto avgSwitchers = std::accumulate(switchers.begin(), switchers.end(), 0.0, [&](double a, double b) { return  a + static_cast<double>(b) / switchers.size(); });
    fmt::print("Average switchers used: {}\n", avgSwitchers);
    fmt::print("Average time: {}\n", avgTime);
    fmt::print("{}\n", secondsToDHMS(avgTime));
}

 

 

  • Metin2 Dev 12
  • Eyes 1
  • Good 3
  • Love 1
Link to comment
Share on other sites

  • Premium
1 minute ago, mihnea said:

This is so cool. The idea of addons of metin is nice. Couldn't you do like something similar to CurseForge on wow and develop and sell addons to players? I think this will come out very profitable.

Me and a friend talked once about doing some sort of "dmg preview" for WoW to know beforehand all the possible dmg variations against a player and/or monsters, but I know very little of WoW
 

Btw this is obviously not a Metin2 addon (like you intend for WoW), more a tool for a server owner to develop the gameplay (I've had to do something similar for my server where all our bonuses use the Gaussian bell). This program can be very well extended to look for more bonuses, with or without the average/skill bonus, extracting the logic from metin2 sources, maybe starting to create a base item class, a table for the item_attr etc.

This is an Item class I had made (real values are for my server so don't mind it, but honestly the more I see it the more I can point to thousands of improvements lol)

enum class ItemType { WEAPON, ARMOR, UNKNOWN_TYPE };
std::istream& operator>>(std::istream& is, ItemType& item_type)
{
	int a;
	is >> a;
	item_type = static_cast<ItemType>(a);
	return is;
}
std::ostream& operator<<(std::ostream& os, const ItemType& item_type)
{
	os << static_cast<int>(item_type);
	return os;
}


const int MAX_VALUES = 5;
const int MAX_UPGRADE = 9;
const int MIN_PHYS_WEAPON_POS = 3;
const int MAX_PHYS_WEAPON_POS = 4;
const int MIN_MAGIC_WEAPON_POS = 1;
const int MAX_MAGIC_WEAPON_POS = 2;

const int MIN_PHYS_ARMOR_POS = 2;
const int MAX_PHYS_ARMOR_POS = 4;
const int MIN_MAGIC_ARMOR_POS = 0;
const int MAX_MAGIC_ARMOR_POS = 1;

class Item
{
	public:
		Item(std::vector<int> vec_values, ItemType item_type);
		~Item();
		void SetValues(std::vector<int> vec_values) { m_vec_values = vec_values; };
		void SetType(ItemType item_type) { m_item_type = item_type; };

		int GetValue(int value);
		void SetValue(int position, int value);
		std::vector<int> GetValuesVector();
		void GenerateRealValues(int value5);
		ItemType GetItemType() { return m_item_type; };
		void PrintRealValues();
		void PrintValues(bool toFile = false);
		int GetMinPhysValue() { return real_phys_min_value; }; //to do: struct
		int GetMinMagicValue() { return real_magic_min_value; };
		int GetMaxPhysValue() { return real_phys_max_value; };
		int GetMaxMagicValue() { return real_magic_max_value; };
		void SetName(std::string item_name) { m_item_name = item_name; }
		std::string GetName() { return m_item_name; }
		void PrintItemValues();

	private:
		std::vector<int> m_vec_values;
		ItemType m_item_type;
		int real_phys_min_value = 0;
		int real_magic_min_value = 0;
		int real_phys_max_value = 0;
		int real_magic_max_value = 0;
		std::string m_item_name = "";
};

 

  • Good 1
Link to comment
Share on other sites

  • Premium

I gave the example of WoW with it's addons and metin2 that has none of that stuff. And maybe some players would pay to improve their gameplay . It was an idea to build something directed to players, as everyone on this forum ( not me ) has a ton of knowledge about metin and only advertises towards dev's ( a considerably smaller audience) rather than players.

Great work!

Link to comment
Share on other sites

  • Premium
5 minutes ago, mihnea said:

I gave the example of WoW with it's addons and metin2 that has none of that stuff. And maybe some players would pay to improve their gameplay . It was an idea to build something directed to players, as everyone on this forum ( not me ) has a ton of knowledge about metin and only advertises towards dev's ( a considerably smaller audience) rather than players.

Great work!

You mean tools for players to use to improve an already existing server (like giving feedback to a server owner) or just to look for example how much dmg they could do to something and stuff? I mean, yeah, sure, although obviously on private servers it's a bit tricky, I can do a whole program that checks damages, percentages and what not, but it will be based on w/e sql files I have on hand and obviously, every server owner has it's own stuff (well, tbf I could load data from txt and every owner could use their txts. It would actually be a nice side project 🤔)

Edited by Intel
Link to comment
Share on other sites

  • Premium

An example would be : An ingame simulator of dmg so that it calculates your best item in terms of dmg ( I have equipped a RIB and when I mouseover another RIB in my inv it could show me under its bonuses - whats the DPS increase - or if its is any at all - or maybe " You will do 3,4% more dmg to Undead" ( bcs the item might have low avg but 20% undead on it ) . Fuck me I'd be paying for this . And you can sell it to players (thousands) not only devs

Or maybe like steam overlay - when you press Shift-Tab for example - it opens an overlay that shows the best combos for your character - it makes a good pvp addon . And you can showcase the fluidity of your new sync position system in the videos. For example its shows the combo for dagger . Or an addon which shows a small video for best way to farm in monkey dungeons - and maybe highlights a spot where u should sit in monkey dungeon on the minimap. Hahaha Idk you do great porgramming work so the possibilities are endless - and you could make a ton.

And yeah it won't work on some highly customized private servers - but everyone plays official like either way. Who tf is interested in some shitty ass new maps with new mobs that aren't thought of my Ymir?

Link to comment
Share on other sites

  • Premium
1 hour ago, mihnea said:

An example would be : An ingame simulator of dmg so that it calculates your best item in terms of dmg ( I have equipped a RIB and when I mouseover another RIB in my inv it could show me under its bonuses - whats the DPS increase - or if its is any at all - or maybe " You will do 3,4% more dmg to Undead" ( bcs the item might have low avg but 20% undead on it ) . Fuck me I'd be paying for this . And you can sell it to players (thousands) not only devs

Or maybe like steam overlay - when you press Shift-Tab for example - it opens an overlay that shows the best combos for your character - it makes a good pvp addon . And you can showcase the fluidity of your new sync position system in the videos. For example its shows the combo for dagger . Or an addon which shows a small video for best way to farm in monkey dungeons - and maybe highlights a spot where u should sit in monkey dungeon on the minimap. Hahaha Idk you do great porgramming work so the possibilities are endless - and you could make a ton.

And yeah it won't work on some highly customized private servers - but everyone plays official like either way. Who tf is interested in some shitty ass new maps with new mobs that aren't thought of my Ymir?

Jesus, I was thinking of only some damage visualizer for testing, you went beyond that with "small videos examples" 😄

Btw the integration with existing clients (even the official one) would be tricky because maybe considered a "cheat" (and I would avoid using overwolf even if it was a possible thing to use for metin2, f*** overwolf), but a standalone program is doable (videos aside  because 1: I'd need those videos 2: it will take a tiny bit more time ahah)

  • Love 1
Link to comment
Share on other sites

Announcements



×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.