Closed AR-234 closed 2 years ago
I agree with you, I often had similar needs and in fact I was planning to implement exactly what you suggest at the end: a predicate that says "at least N predicates in the following list hold". I was thinking of a syntax like:
count [
has "ChaosResist6";
has "Dexterity4";
open_prefix
] >= 2
which is very close to your suggestion.
Another orthogonal idea I want to get to at some point is to be able to express things like "tier 4 or better". It could look like this:
has_group "IncreasedLife" tier <= 4
although I'm not fond of this particular syntax proposal, and I'm not sure how to extract tier information yet from the game data (maybe I can just extract the number from the mod name and reverse it, but there may be cases where GGG were not very consistent in their names?…).
One could even combine the two ideas to be able to express something like "the sum of the tiers of resistance mods is 4 or better". For instance:
tier_sum [ "FireResist"; "LightningResist"; "ColdResist" ] <= 4
Assuming that the absence of a mod group counts as having tier 999 or something, the above effectively means "all 3 resists are present as T1 except one which is T2".
Another idea would be to be able to have variables (not necessarily mutable though) to avoid repeating stuff, like:
let $count = count [ has "ChaosResist6"; has "Dexterity4"; open_prefix ]
if $count >= 2 then gain 2 exalt
if $count >= 3 then gain 3 exalt
(the dollar $
is because this is a very keyword-heavy language and it makes sure that variable names do not become keywords later, which would break existing scripts).
I need to spend a bit more time to design something good and you're right, it may be more consistent, if we do that, to also have prefix_count
be used like prefix_count <= 2
. We may want to be able to define intervals like 1 <= prefix_count <= 2
(i.e. prefix count is between 1 and 2).
All of these are possible, but I want the result to be consistent and not look like a patchwork of ideas that don't play well with each other, so it'll take a bit of time to get right.
yes mod tiers.. for example flasks, the lowest tier is 1 in the id for boots it is the other way around were for example Dex T1 has Dexterity9 I hope that it is a per base thing, but that needs more research. I was thinking maybe it is better to sort the mod group by ilvl required so you get the tier. (would only need a small script to verify, if and mod groups have the same ilvl for 2 mods or if all are in order)
the first approach with a condition list is a good one combined with the variables this would eliminate a lot of repeating code.
maybe even a more php like approach were even let isn't there, but a keychar $ is absolutly needed and makes sense even from a readability point of view.
Because if a command line is $<variable> = <int/bool>
I also like the interval approach.
Maybe even something like this:
$count = count [
has "ChaosResist" == 1;
has "Dexterity" <= 2 ;
has "Strength" <= 2;
2 <= has "FireResist" <= 4;
has "MovementVelocity";
]
$limit = count [
has "Dexterity";
has "Strength";
]
if $count >= 2 and $limit == 1 then gain 2.5 exalt
in my example has would always check mod group and resolve them always to booleans.
has "MovementVelocity"
would resolve to has any movement speed mod if so true otherwise false
the other onces are quite self explaning
also the ending of every list entry with a semicolon is not by mistake, would reduce risk to forget one, maybe allow it but not enforce it? so the last entry can have a semicolon but doesn't need it.
with proper syntax highlighting I think this can look kind of clean and it is faster to write code for then looking up every id, atleast that is my hope
So a little todo list would be:
and the rest would need a complete overhaul
Sorting by ilvl is probably the best way, you're probably right, good idea!
Agree with allowing trailing semicolons. This allows to move lines around without having to fix semicolons.
Here is something else to consider: be able to specify a value for each mod. For instance:
$value = case_sum
| has "ChaosResist" tier 1 -> 10
| has "Dexterity" tier 1 -> 5
| has "Dexterity" tier 2 -> 2
| has "Strength" tier 1 -> 5
| has "Strength" tier 2 -> 2
if $value < 17 then goto .restart
gain $value exalt
This effectively means "must have chaos res T1, must have dex and str at least T1 and T2, then sell the item for the sum in exalt"
Syntax is just me playing around with ideas but basically I think it could be useful to assign value to each mod and be able to do stuff with the sum of the values.
I think this would be a little bit to complicated to wrap your head around while writing this because prices are not always fixed and if a price changes you have to change a lot of values.
or if you want to keep this feature just to give options like this:
case_sum $value
with has "ChaosResist" = 1 for 10 chaos
with has "Dexterity" = 1 for 10 chaos
with has "Dexterity" = 2 for 5 chaos
with has "Strength" = 1 for 10 chaos
with has "Strength" = 3 for 5 chaos
just writing something that is elsewhere in use but this would look cleaner
case_sum $value
| has "ChaosResist" = 1 for 10 chaos
| has "Dexterity" = 1 for 10 chaos
| has "Dexterity" = 2 for 10 chaos
| has "Strength" = 1 for 10 chaos
| has "Strength" = 2 for 10 chaos
also just to post it count would look with this like
count $value
with has "ChaosResist" = 1
with has "Dexterity" = 1
Actually after sleeping over it I think I have a simpler solution. One could write something like this:
$value =
5 * (3 - tier "ChaosResist") +
4 * (2 - tier "Dexterity") +
4 * (2 - tier "Strength")
For instance, with T1 chaos res, T2 dex and T1 strength, value is 14.
If variables are mutable, you can also write:
$value = 0
if tier "ChaosResist" = 1 then $value += 10
if tier "ChaosResist" = 2 then $value += 5
if tier "Dexterity" = 1 then $value += 4
if tier "Strength" = 1 then $value += 4
for a similar result, which may be more readable (but a bit more verbose).
I think this would be easier to understand and more general than the case_sum operation I proposed earlier.
What is needed to achieve this:
tier <mod_group>
expression, returning an int (if mod group is not present, returns 999 or an error?)+ - / *
< > <= >= <> =
goto
)$x +=
as a short-hand for $x = $x +
(and -= *= /=
)This is not that much work I believe and would provide a lot of expressive power.
yeah probably the best approach to the problem. I guess wouldn't hurt if there is no mod group to set the tier to 1, since it is the tier 1 of that mod since there is only one?
A thing we need to look out for is the orb of dominance since they are higher in the list but are named tier "E" for elevated so no clue how we should handle that maybe as tier 0 internally but allow as E aswell?
if tier "ChaosResist" = E then $value += 100
which would be the same as
if tier "ChaosResist" = 0 then $value += 100
or should we just stick with Tier 0 for them?
Also for optimization purposes i was thinking to calculate the tier of each mod when creating the cache and writing it in there.
Yes, I planned to cache tiers. But actually it's more complicated than that: tiers depend on the item. For instance, T1 life is not the same mod on a body armour and on gloves. So my plan is to store global tiers in the cache and also have in-memory caching (memoization) for local tiers.
That being said, I had a quick look and reached unfortunate conclusions:
However, it looks like detecting elevated mods is easy:
Maven
in their id;Elevated ...
or of Elevated ...
or of the Elevated ...
.I wonder how Craft of Exile does it.
nebuchenazarr (Craft of Exile):
I group things by mod name + affix type (prefix, suffix) + affix group (base, elder, crafted, etc). If these all match its assumed that they are the same mod and thus tiers of that mod. Then they are ordered by ilvl. In rare cases where they have the same ilvl they are ordered by values. Since i group by mod name i had to do some custom handling for special cases where the wording change even though its the same mod. Like with "an additionnal arrow" and "additionnal arrows" not grouping together. So its not perfect but these cases are rare.
Regarding Maven he thinks he has done it with the ID containing Maven
That's very useful, thanks!
I have pushed a commit that causes the mod tiers to be displayed next to the modifiers. For instance:
--------
Citrine Amulet (Rare)
--------
(prefix) [T1] 26% increased Spell Damage (SpellDamage5)
(prefix) +1 to Level of all Fire Skill Gems (GlobalFireGemLevel1_)
(suffix) [T1] 24% increased Fire Damage (FireDamagePercent5)
(suffix) [T1] 20% increased Cast Speed (IncreasedCastSpeed4)
(suffix) [T4] +30% to Cold Resistance (ColdResist5)
(prefix) {crafted} +49 to maximum Life (EinharMasterIncreasedLife3)
--------
As you can see it doesn't display a tier for +1 to Level of all Fire Skill Gems
and it actually makes sense because the mod group has all the +1 to Level of all X Skill Gems
mods but they all have the same tier basically. In other words tiers do not make sense for this particular mod group because tiers would not define a total order.
It may be annoying for weapons where there are two tiers of +X to Level of all X Skill Gems
, and we can fix that later by considering that all those mods are Tier 1 or something, but as a first implementation I think it is quite satisfying. Now I can actually add expressions like tier X <= Y
and they will work for most mods, including stuff like resists and attributes where it would be quite useful.
My implementation would not immediately support elevated mods yet but I think I should still be able to implement orb of dominance based on it.
I have pushed a commit which implements arithmetic operations and comparisons, with the possibility to use prefix_count
, suffix_count
, affix_count
and tier <mod_group>
. I have also added an example:
# This recipe shows how to use arithmetic and tiers to express complex conditions
# on modifiers.
# We start from an ilvl 100 Agate Amulet.
buy "Metadata/Items/Amulets/Amulet9"
# Then we chaos spam until the sum of the tiers of attribute modifiers
# is 6 or less, meaning that we have all three modifiers in one of the following combinations:
# - T1 + T1 + T4 or better
# - T1 + T2 + T3 or better
# - T2 + T2 + T2 or better
until
tier "Dexterity" +
tier "Intelligence" +
tier "Strength" <= 6
do
chaos
I still need to document this in the manual.
Arithmetic expressions are now documented in the manual.
Now we may still want to count the number of predicates that hold. Maybe we can have a way to convert a predicate to an integer, e.g. using brackets like [has X] + [has Y] + [tier Z <= 3]
.
And we may also still want variables.
A problem I am currently still facing with the current implementation is that I got a lot of disired mods for example: Fire/Cold/Lightning/Chaos Resistance Dexterity/Strength SpellSuppression
I only want to keep items that have atleast t3 mods and all suffixes taken by one of the list above.. so its something like (but not really since a t5 and a t1 mod would also match, if tiers that are not found are ignored..)
buy "Metadata/Items/Armours/Boots/BootsStrDex8"
until
tier "ColdResistance" +
tier "FireResistance" +
tier "LightningResistance" +
tier "ChaosResistance" +
tier "Intelligence" +
tier "Dexterity" +
tier "ChanceToSuppressSpells" <= 6
do
chaos
but with this I end up in an infinte loop, hadn't really the time to check how you handle if a mod is not found but I imagen you give it a real high number which leads to an infinite loop once again :)
maybe a function that counts boolean expressions would still be helpful since that kind of stuff would be cleaner to read and possible more intuitive for users to implement, since the trade site got filters like and (implemented) not (implemented) if (implemented) count (not really implemented) weighted sum (not implemented)
also with a count function you could implement far more complex statement then in the current setup or am i missing something? :D
Indeed your example doesn't work because tier
returns 999 if no mod with the requested mod group exists. If tier
returned 0 instead, it would not be better: your condition would be true for items that have none of the requested mods.
So yes, we need a way to count predicates. I see two choices.
Choice 1 — Predicates As Ints
One would be able to convert any condition into an integer, with true = 1 and false = 0, using brackets [ ... ]
.
Example to count the number of attribute mods:
[tier "Strength" <= 20] + [tier "Intelligence" <= 20] + [tier "Dexterity" <= 20]
This returns a number between 0 and 3 corresponding to the number of attribute mods.
You can even use weights. For instance, let's say that you like Dexterity more than Intelligence, and Intelligence more than Strength. You could write:
[tier "Strength" <= 20] + 2 * [tier "Intelligence" <= 20] + 3 * [tier "Dexterity" <= 20]
Pros:
Why brackets by the way? Why not just allow has "Strength8" + has "Intelligence7"
?
tier "Strength" <= 5 + 7
actually means (tier "Strength" <= 5) + 7
and not the more obvious tier "Strength" <= (5 + 7)
; also, I think Kalandralang would have a hard time parsing prefix_count
correctly because it can be both a condition and an expression right nowChoice 2 — A Dedicated count
Operation
We already discussed this and gave examples in this thread.
Pros:
I'm currently leaning heavily towards choice 1.
I would say approach 1 is also better since it enables weights aswell, but I dislike the <= 20
to filter out the 999, because it isn't really intuitive (if you know the code / background its obvious, otherwise it isn't really)
which would be a pro for count since it is more readable as you already said.
also the weights would be weired since t1 would be worse then t3 lol, but could be done with something like 3 * (1 / [tier "Dexterity" <= 20])
, but what is the maven tier.. if it's 0 this will not work anymore..
maybe the anwser is just both.. most languages do have for and while and they are mostly the same except that one is more like a raw version and the other one is a shortcut to a longer more complex problem..
About using <= 20
to filter out 999: I agree it's not very readable. We can add a function has_group
instead:
[has_group "Strength"] + 2 * [has_group "Intelligence"] + 3 * [has_group "Dexterity"]
About 3 * (1 / [tier "Dexterity" <= 20])
: did you mean 3 * (1 / tier "Dexterity")
? I would use a subtraction instead:
3 * (2 - tier "Dexterity")
Value is 6 for tier 0, 3 for tier 1, 0 for tier 2, -3 for tier 3, etc. We may need a way to cap it at 0, such as 3 * max 0 (2 - tier "Dexterity")
but it's not very intuitive when you're not used to it.
Currently if you want to give more value to better tiers you can write:
[tier "Dexterity" <= 1] + [tier "Dexterity" <= 2] + [tier "Dexterity" <= 3]
the value of this is 1 for T3, 2 for T2, 3 for T1.
It's still not ideal. It's probably more readable to just use if
but it would be better with variables:
$value = 0
if tier "Dexterity" <= 3 then $value = 4 - tier "Dexterity"
or with if
as expressions:
if tier "Dexterity" <= 3 then 4 - tier "Dexterity" else 0
or
4 - (if tier "Dexterity" <= 3 then tier "Dexterity" else 4)
which is the same as
4 - (min 4 (tier "Dexterity"))
None of these is very readable but count
would not help here I think.
I think we just need to write more recipes and see what we often use in practice to see how we would want to write it. I try to avoid generalizing a solution without at least 3 use cases :) But I do believe that the bracket thing would be useful.
One Thing to consider ist you would have to write the highest Tier always in to the code.
Which isnt optimal.
By "in the code" do you mean in recipes? I don't think it is needed since we often don't care about low-tier mods and we can just ignore tiers lower than, say, 4. For mods with only 2 or 3 tiers, we can just use the mod names directly with has
.
oh yeah mistake on my part you are right. (and yes i meant in the recipe)
I have implemented the bracket syntax. Here is an extract from the user manual:
buy "Metadata/Items/Amulets/Amulet9"
until
[tier "Intelligence" <= 3] +
[tier "Dexterity" <= 3] +
[tier "Strength" <= 3]
>= 2
do chaos
is a recipe that uses Chaos Orbs on an Agate Amulet until it has at least two attribute modifiers, both of them tier 3 or better.
I think this is a good first step. Let's play with it a bit and see if it is enough in practice.
awesome thanks alot, will close this since it is done with this update, if I find anything not behaving right, will try to fix it or report it <3
I want to craft an item were a combination of different mods is allowed. I got a list of mods (6 t1, 5 t2) that are good to keep, but the item needs atleast 2 to be worth it and craft further. How would I do this?
Currently I would need to have a if statement with every combination or am I wrong?
Was thinking of a overhaul of the condition system e.g.
if prefix_count > 2 then
Then integers could be added with basic functinality to solve problems like that, or even counters that are integers but only with simple instructions likethis is obiously not tought through just a quick mock up
an other approach would be to have a list and check how many of the conditions are true..
if 2 of [has "ChaosResist6", has "Dexterity4", has "ChanceToSuppressSpells5__"]
What do you think about this problem?