Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rule engine for item price calculation #75

Open
sven-n opened this issue Oct 25, 2018 · 3 comments
Open

Rule engine for item price calculation #75

sven-n opened this issue Oct 25, 2018 · 3 comments

Comments

@sven-n
Copy link
Member

sven-n commented Oct 25, 2018

Current state

The item price calculation is pretty complicated with a lot of special logic - it‘s leaned on the original price calculation function. However, the code knows too much about the data - it should not know item numbers at all. It’s impossible to add a new item which requires special logics without touching the code.

Goal

Pricing rules defined by data, referenced by the ItemDefinition. The calculation just works on these rules, without special logics which handles specific item numbers. Ideally, new items should never require code extensions.

How

There could be different kind of pricing rules, e.g.:

  • Fixed price (e.g. Jewels)
  • Fixed price defined for each item level (e.g. Blood Bones)
  • Automatic (e.g. Armor), which applies multiple rules for different kinds of options
@guser9822
Copy link
Contributor

guser9822 commented Aug 12, 2020

Hello @sven-n, I'm trying to complete this issue, but I have a lot of doubts on how realize this new price calculation based rules. So, before asking questions I started on working on the solely thing that I think that I understood : the creation of the price rules.

I have created a branch on my repository that contains all the changes made unitl now. I started working on buy price calculation and basically I decomposed the original function in different price calculation rules and modified accordingly the buy price calculation with a new function: whenever you buy something, the price of the item pass through all the rules and gets modified whenever a rule can be applied based on the item definition. As you can see from the original function, there's an order to follow when calculating the price, and that's why I ordered the execution of the rules in this manner and for some items(e.g. scrolls, rings) must be applied exactly one rule, while, these rules must be applied to all the items. Running the price calculation test with this new structure I get the same success/fail as with the original function (and it sound strange to me that even with the original function I get some fail, e.g. bone set test).

All the code I wrote is ugly, I just needed a fast prototype, so please apologize me for this mess. I recognize that I only splitted and moved the logic from one place to another, so it's not so much, and that's why I'm asking your help, because I believe, reading the issue, that you are pointing in a whole different direction (maybe persisting the price rules, for each item, in a specific table ? ). Thank you in advance.

@sven-n
Copy link
Member Author

sven-n commented Aug 13, 2020

Hello, thank you! I'm currently prettly busy in real life, so I'm sorry for my late and short answers.

Running the price calculation test with this new structure I get the same success/fail as with the original function (and it sound strange to me that even with the original function I get some fail, e.g. bone set test).

Great! I didn't recognize the failing unit tests, but I will look at them as soon as I have more time - hopefully in a few weeks.

All the code I wrote is ugly, I just needed a fast prototype, so please apologize me for this mess. I recognize that I only splitted and moved the logic from one place to another, so it's not so much, and that's why I'm asking your help, because I believe, reading the issue, that you are pointing in a whole different direction (maybe persisting the price rules, for each item, in a specific table ? ). Thank you in advance.

It's okay! That you splitted and moved that logic is exactly the plan. I'm a fan of implicit logic by convention where possible - that means e.g. if an item has excellent option, some kind of exc option rule should automatically apply. I also want to get rid of item ids in the code - that's why I wanted some kind of configuration in the database. So, we need some kind of explicit configuration, but only for all these special cases. I think when nothing is configured for an item, the automatic logic should apply - otherwise an explicit rule, which must be configured in another table(s).

@guser9822
Copy link
Contributor

Thank you so much for your reply @sven-n , now I have a clearer idea of ​​what to do, but nevertheless I still have some doubts.

It's okay! That you splitted and moved that logic is exactly the plan. I'm a fan of implicit logic by convention where possible - that means e.g. if an item has excellent option, some kind of exc option rule should automatically apply. I also want to get rid of item ids in the code - that's why I wanted some kind of configuration in the database. So, we need some kind of explicit configuration, but only for all these special cases. I think when nothing is configured for an item, the automatic logic should apply - otherwise an explicit rule, which must be configured in another table(s).

The fact is, a price rule can only be applied if a certain condition is met. To be clear, suppose we want to calculate the price of a shield, then the following price calculation rules are applied in the following order:

  1. BasePriceRule:
if ( ! IsWing(item)) 
{ 
 price = ((dropLevel + 40) * dropLevel * dropLevel / 8) + 100;
}
  1. ShieldOrHandedPriceRule:
if (isOneHandedWeapon || isShield) 
{ 
 price = price * 80 / 100;
}
  1. If the shield have luck, skill or exc opts other 3 price rules......

  2. MaximumPriceRules:

if (price > MaximumPrice) 
{ 
 price = MaximumPrice;
} 
  1. MaximumDurabilityPriceRules:
 if (maxDurability > 1 && maxDurability > item.Durability)
 {
   float multiplier = 1.0f - ((float)item.Durability / maxDurability);
   long loss = (long)(price * 0.6 * multiplier);
   price -= loss;
 }

As I intended, a price rule is just a function that contains a condition that, if it is met, an arithmetic expression is executed. The rules are automatically assigned if you test an itemDefintion against each of them. So, my question is, does it make really sense storing in the database this rules list and rules list for each item?? Even if you manage to store those predicates in the database (e.g. Linq expression serialization/deserialization) you would still need to access them somewhere in the code in order to modify them.

Since I don't see any benefits in storing price rules as configuration (but maybe I'm missing something), I would suggest a different approach, that is, for each itemDefinition created during the game (game initialization, new drop item etc. etc ), attach to it a list of price rules that match with this definition. This list, transparent to the database (transient), it will be used by the item price calculator to determine the buy price.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants