-
Notifications
You must be signed in to change notification settings - Fork 773
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
Rework DynamicValue implementations #12596
base: master
Are you sure you want to change the base?
Conversation
This reverts commit 585dd63.
…multiplier behavior and maximize flexibilty with number of/for each phrasing
Skipped the writeup: This new strategy just focuses on making sure all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some interesting ideas here. solving the boost text is a solid step, since that has the substantial complication of two dynamic/static values together.
I'd like to see an example for a more straightforward effect as well, like DrawCardSourceControllerEffect
Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java
Outdated
Show resolved
Hide resolved
…ge based on whether the phrasing calls for it
…hoDrawCard argument, it's always just "you")
I made the "for each" phrasing the default, since scryfall indicated that that's the most popular phrasing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make sure to look at the other decorator classes too, not just MultipliedValue
@@ -16,24 +15,38 @@ | |||
*/ | |||
public class DrawCardSourceControllerEffect extends OneShotEffect { | |||
|
|||
public enum DrawCardsPhrasing { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this being nested here - seems like there should be one public enum for all usages
Mage/src/main/java/mage/abilities/effects/common/DrawCardSourceControllerEffect.java
Outdated
Show resolved
Hide resolved
StringBuilder sb = new StringBuilder(youDraw ? "you " : ""); | ||
sb.append("draw"); | ||
String value = " a"; | ||
if (amount instanceof StaticValue) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, I wonder if STATIC should be part of the enum, since it'll generally need its own text gen case and that way could just be part of the switch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now, detecting instances of StaticValue
is already necessary in Effects when they need to call getValue
, so this wouldnt significantly improve the text generation pattern. That is, unless there is a pattern that doesnt involve needing to call instanceof StaticValue
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's ok to use instanceOf for popular and basic classes. Just make sure it do not break cards with extended class versions.
Mage/src/main/java/mage/game/command/emblems/ObNixilisOfTheBlackOathEmblem.java
Show resolved
Hide resolved
value = " X"; | ||
} else if (drawCardsPhrasing == DrawCardsPhrasing.EQUAL_TO) { | ||
value = ""; | ||
} else if (amount instanceof MultipliedValue) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generally not a fan of these instanceof checks, but open to admitting that it's the least bad approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach is probably going to lead to making a DecoratedValue
abstract class (or interface?) for instance checking like this, since all the decorated values will need their qualification put into the text in this manner.
Another possible approach is to make decorator classes and StaticValue
inherit a common class or interface (maybe still named DecoratedValue
, though having StaticValue
inherit that seems wrong to me) that has a getValue()
function (or something to descriptively replace toString()
) that goes in place here:
Draw <getValue()
/""> cards <for each/where X is/equal to> getMessage()
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be possible to have the method in DynamicValue itself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, i'm not sure how to avoid the instanceof
checks. Technically, some unique behavior could be implemented that identifies instances of MultipliedValue
and StaticValue
such that they can be handled in places like this, but i'm realizing two things that make this kinda pointless:
StaticValue
,MultipliedValue
, and the otherDynamicValue
implementations must all be handled differently (3 broad cases)- I can't think of any other cases than these three. the other decorator classes haven't necessitated property handling like 'MultipliedValue' has.
AdditiveDynamicValue
andIntPlusDynamicValue
only need to modify theirgetMessage
calls, andLimitedDynamicValue
andLimitedDynamicValue
are used in one place each, so trying to generalize text for them doesn't seem worth it.
So trying to avoid using instanceof
will still land us handling these classes specially, at which point why not use instanceof
…cts need to reference when building oracle text
…enerator for SetBasePowerToughnessSourceEffect for use in AbominationOfLlanowar
…r DamageTargetEffect and static text for SetBasePowerToughnessPlusOneSourceEffect
…itedDynamicValue (used in one place each, so not worth tweaking text generators for)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also need report with full rules stats in master and in your PR -- how many rules fixed in that PR.
VerifyCardDataTest.test_verifyCards
with FULL_ABILITIES_CHECK_SET_CODES = "*"
public String getMessage() { | ||
return this.dynamicValues.stream().map(DynamicValue::getMessage).collect(Collectors.joining(" ")); | ||
public String getMessage(EffectPhrasing phrasing) { | ||
if (phrasing == EffectPhrasing.X_HIDDEN){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use case for phrasing and throw exception on default (e.g. on unsupported)
Mage/src/main/java/mage/abilities/dynamicvalue/MultipliedValue.java
Outdated
Show resolved
Hide resolved
// return Integer.toString(multiplier); | ||
// } | ||
// StringBuilder sb = new StringBuilder(); | ||
// if (multiplier == 2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not keep commented code or add explanation about it in the first line like "old version" or "TODO: need check all used cards"
Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java
Outdated
Show resolved
Hide resolved
Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java
Outdated
Show resolved
Hide resolved
StringBuilder sb = new StringBuilder(youDraw ? "you " : ""); | ||
sb.append("draw"); | ||
String value = " a"; | ||
if (amount instanceof StaticValue) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's ok to use instanceOf for popular and basic classes. Just make sure it do not break cards with extended class versions.
} else if (toughness.getSign() < 0){ | ||
t = "-" + t; | ||
} | ||
|
||
// sign fix for zero values |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1
Looks like duplicated code for sign values above (new) and below (old). Must use one place to set sign text.
2
Also I don't like "else if" parts -- looks like it will be used for dynamic value only (but your comments about manual sign). Must move to static part instead?
//Non-static values will need their minus sign manually inserted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like duplicated code for sign values above (new) and below (old). Must use one place to set sign text.
Not sure what the duplicate code youre referring to is. do you mean that within this function, there is more than one statement prepending a minus sign?
Also I don't like "else if" parts -- looks like it will be used for dynamic value only (but your comments about manual sign). Must move to static part instead?
That would be the opposite of the goal for this statement... It's an else if so that only non-StaticValue
classes make it there, since they would already have their minus sign from the Integer.toString
call. Is the ask to separate any handling of static and dynamic values in this function so they're not interleaved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JayDi85 I'm holding up moving forward with the bulk of implementations for this until we resolve this
Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java
Outdated
Show resolved
Hide resolved
Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java
Show resolved
Hide resolved
@@ -98,6 +99,11 @@ public BoostAllEffect copy() { | |||
return new BoostAllEffect(this); | |||
} | |||
|
|||
public BoostAllEffect usingPhrasing(ValuePhrasing textPhrasing) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withTextPhrasing for consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry to be nitpicky, but I think the standard is "with" not "using"
@@ -34,7 +44,6 @@ public IntPlusDynamicValue copy() { | |||
return new IntPlusDynamicValue(this); | |||
} | |||
|
|||
@Override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mage/src/main/java/mage/abilities/effects/common/DamageTargetEffect.java
Show resolved
Hide resolved
sb.append("X"); | ||
} else if (textPhrasing == ValuePhrasing.EQUAL_TO) { | ||
// do nothing | ||
} else if (amount instanceof MultipliedValue) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t like that style of code:
- it’s full of potential bugs and miss combinations (you combine phrasing param and class type in one switch instead split it);
- it will be miss on new ValuePhrasing changes (no throw exception on unknown value);
- it’s overdeveloped - instead using on two lines of code it required 10+ lines.
Must use switch with throw error all the time, not “if else”. Current code smells bad. I don’t look at other effects. But if it use/require same logic then whole thing must be rethink/rework.
P.S. Also look at legacy part - it miss empty check in new code. Is that fine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intricacies of templating require a lot of parameters to generate text correctly. It's not necessarily "overdeveloped".
I agree that a switch would be cleaner, but it is not obvious to me that would in fact be a workable solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also:
- the old code doesn't correctly generate text in many common scenarios
- if getText method doesn't correctly generate text, it doesn't impact game logic or stability
- any discrepancies are easily caught by the verify check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For possible ideas: I like a text template style for complex use cases instead multiple if/else (text replacing is slower, but much more easy to edit and support).
// prepare template
template = “some [VALUE] in the middle and [OTHER PARAM]…”;
// prepare params
realValue = xxx;
realParam = yyy;
// gen text
template.replace(“[VALUE]”, realValue);
template.replace(“[OTHER PARAM]”, realParam);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think i see what you're saying here:
- this needs to throw an exception for unsupported phrasings
- if-else if chains are inherently less readable and less flexible
I took a shot at changing DamageTargetEffect
and DrawCardSourceControllerEffect
to use switch statements here. Right now those are the two effects that are being used as a test bed for this pattern.
What remains are the instanceof
checks for MultipliedValue
and StaticValue
complicating such fragments. The base DynamicValue
class /could/ be made to own states like being static or having a multiplier, but that would require making it into a class, which we've previously decided against.
If this is still too spaghetti, i'm open to ideas for how this should work instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For possible ideas: I like a text template style for complex use cases instead multiple if/else (text replacing is slower, but much more easy to edit and support).
// prepare template template = “some [VALUE] in the middle and [OTHER PARAM]…”;
// prepare params realValue = xxx; realParam = yyy;
// gen text template.replace(“[VALUE]”, realValue); template.replace(“[OTHER PARAM]”, realParam);
This and xenohedron's comments just showed up for me for some reason--I can get behind this too, but im curious to see if you think the switch statment works first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree that the new switch is better. (Ideally you'd have one switch instead of two switches on that enum, but I'm not sure whether or not trying to combine them would be overall cleaner.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a solid case for the templating idea from JayDi, now that i think about it.
@@ -28,10 +30,18 @@ public DrawCardSourceControllerEffect(DynamicValue amount) { | |||
this(amount, false); | |||
} | |||
|
|||
public DrawCardSourceControllerEffect(DynamicValue amount, ValuePhrasing textPhrasing) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm already running a complete oracle text check with Is this not the case? Or is the ask to add some verification in the branch so everyone can review the same check? |
Yes. If you checked global stats after each your changes and no degrade found then all fine. No needs in branches and other. Just make sure your changes/replaces really work. |
The templating is definitely better. Forces me to search for the variations on phrasings that dont necessarily have to do with the |
@JayDi85 lemme know what you think of the most recent changes to DrawCardSourceControllerEffect and DamageTargetEffect. If the approach looks OK i will move on to implementing the new 'getMessage' for DynamicValues in stages. |
How many classes will require changes such as DrawCardSourceControllerEffect? |
By the end of this whole rework, all effects that use DynamicValue (My search turns up 108, but there are probably more) will have their text generation adapted to the new phrasing scheme. Right now however. no effects require changing for this to work. This change maintains backwards compatibility until all the work is done, at which point all effects will have the new phrasing scheme, where the default instance of each effect should generate the same text as its legacy phrasing (albeit using the new templating). Then, i can remove all the legacy text generation (this will be all uses of the |
@JayDi85 Lemme know if that sounds acceptable. |
#12595
This PR is to get the new pattern established with specific examples.
This will certainly not pass build, but it does demonstrate the solution I've landed on, and I'm presenting this for review before i continue.I found it will generally be easier to go with the solution @xenohedron proposed here, since there are some values that need to be phrased as if they were an amount, rather than a count (power or toughness, versus creatures on the battlefield, for example).
The big implication of this is that in order to own amultiplier
,DynamicValue
had to become an abstract class, rather than an interface.I plan to use this PR to continue implementing this, which will certainly hit every single subclass ofDynamicValue
as well as many effects that use this type, not to mention cleaning up the following:1. Any use of toString() on aDynamicValue
2. All uses of.instance
on aDynamicValue
(since subclasses wont be a singleton anymore)Many effects will need to be reworked to use this phrasing consistently, after which i don't think there will be that much need for effect text overriding just for the purpose of fixing a dynamic value's text.
Open to feedback.