Single conditional with two runs?

Hello all, I have this procedure:

\procedure adjusted-fp-cost(character, spell)
<$let spellbook={{{ [<character>get[spellbook]] }}} advbook={{{ [<character>get[advbook]] }}} IQcp={{{ [<character>get[IQ-cp]] }}}>
<$wikify name="baseskill" text="<$macrocall $name='spell-base-skill' spellbook=<<spellbook>> spellitem=<<spell>> advbook=<<advbook>> IQcp=<<IQcp>> />">
	<% if [<spellbook>has:index<spell>] %>
		<% if [<spell>!match[Reflex]] +[<spell>get[class]!match[Blocking]] %>
			<% if [<baseskill>compare::gt[14]compare::lt[20]] %>
				<$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]subtract[1]max[0]] }}}/> (adj. from <$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]] }}}/>)
			<% elseif [<baseskill>compare::gt[19]compare::lt[25]] %>
				<$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]subtract[2]max[0]] }}}/> (adj. from <$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]] }}}/>)
			<% elseif [<baseskill>compare::gt[24]compare::lt[30]] %>
				<$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]subtract[3]max[0]] }}}/> (adj. from <$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]] }}}/>)
			<% elseif [<baseskill>compare::gt[29]] %><$let baseskillexcedent={{{ [<baseskill>subtract[25]divide[5]trunc[]] }}}> (adj. from <$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]] }}}/>)
				<$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]subtract[3]subtract<baseskillexcedent>max[0]] }}}/></$let> (adj. from <$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]] }}}/>)
			<% else %>
				<$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]] }}}/>
			<% endif %>
		<% else %><$macrocall $name="cost-fp" value={{{ [<spell>get[base_cost]] }}}/>
		<% endif %>
	<% else %>
		Spell not in <<character>>'s Spellbook.
	<% endif %>
</$wikify></$let>
\end

The second conditional <% if [<spell>!match[Reflex]] +[<spell>get[class]!match[Blocking]] %> holds two runs to exclude the tiddler “Reflex”, as well as any <spell> whose field class is “Blocking”. The second run works fine, but the first one (the simplest, actually), doesn’t.

It does work when I remove the second run; so I’m assuming one cannot formulate a single conditional with two runs to check; am I correct?

No, this should work as intended:

<% if [<spell>!match[Reflex]] [<spell>!class[Blocking]] %>

First point: Any filter run with the +/:and prefix takes all outputs of prior runs as its inputs and further filters/transforms them. As a simple example:

  1. [tag[HelloThere]] [tag[Community]]

  2. [tag[HelloThere]] +[tag[Community]]
    image

Here you can see that [tag[HelloThere]] +[tag[Community]] isn’t equivalent to [tag[HelloThere]] [tag[Community]]: it restricts the results of the [tag[HelloThere]] run to only those tiddlers that also have the Community tag. To get the combined results of both runs, you need a blank/:or filter run. This is also the default behavior when you don’t specify a different filter run prefix.

In your case, since you want to remove a single title from the filter results, you could also try this slightly simpler alternative:

<% if [<spell>!class[Blocking]] -[[Reflex]] %>

Second point: The get operator replaces each of its inputs with the non-blank value of that tiddler’s field (as specified by get's parameter). This means that [<spell>get[class]!match[Blocking]] won’t return the name of the <<spell>> tiddler if its class field doesn’t match Blocking; instead, it will give you the value of that field. In this case, since you only care about the field value for true/false display purposes, [<spell>get[class]!match[Blocking]] will work just as well in your conditional. But it’s good to keep the difference in mind for instances in which you do want to preserve <<spell>> in the results.

Also, [<spell>!class[Blocking]] (short for [<spell>!field:class[Blocking]]) is a bit shorter. :slight_smile:

Third, least crucial point:

class is actually one of the fields with a special meaning in the TW core: it’s intended to hold CSS classes, which will be applied to the tiddler as whole. Assuming you haven’t defined a .Blocking class in your CSS, you probably won’t see any unwanted effects even if you’re “misusing” the field. But it’s good to be aware of the possible consequences, and personally I’d recommend using a different field name (perhaps spell-class?) instead.

And if you don’t already have it installed, the Commander plugin is invaluable for renaming fields, as well as a host of other bulk operations.

5 Likes

You least crucial point, the third, seems very crucial in my fearful mind :stuck_out_tongue_winking_eye: I updated the field name as you suggested (while trembling about forgetting a place where it’s called upon, but I seem fine for now ;P)

Thanks for the clear explanations on the rest.

Hey again :slight_smile: Apologies for calling again upon your eyes, mine don’t see what’s wrong with the following :stuck_out_tongue:

I’m refactoring the adjusted-fp-cost procedure above, in order to be able to use it for two different fields, base_cost and additional_cost, but more importantly in order to parse its results for formatting the costs with my <<fp-cost>> macro in a more generalizable way (i.e. so it works with fields that don’t contain just a number)

\procedure adjusted-fp-cost(character, spell, field-name)
<$let spellbook={{{ [<character>get[spellbook]] }}} advbook={{{ [<character>get[advbook]] }}} IQcp={{{ [<character>get[IQ-cp]] }}}>
<% if [<spellbook>has:index<spell>] %>
	<$list filter="[<spell>get<field-name>split[ ]]" variable="field-split">
		<% if [<field-split>regexp[\d+$]] %>
			<$wikify name="baseskill" text="<$macrocall $name='spell-base-skill' spellbook=<<spellbook>> spellitem=<<spell>> advbook=<<advbook>> IQcp=<<IQcp>> />">
				<% if [<spell>!spell-class[Blocking] -[[Reflex]]] %>
					<% if [<baseskill>compare::gt[14]compare::lt[20]] %>
						<$macrocall $name="cost-fp" value={{{ [<field-split>subtract[1]max[0]] }}}/> (adj. from <$macrocall $name="cost-fp" value=<<field-split>>/>) 
					<% elseif [<baseskill>compare::gt[19]compare::lt[25]] %>
						<$macrocall $name="cost-fp" value={{{ [<field-split>subtract[2]max[0]] }}}/> (adj. from <$macrocall $name="cost-fp" value=<<field-split>>/>) 
					<% elseif [<baseskill>compare::gt[24]compare::lt[30]] %>
						<$macrocall $name="cost-fp" value={{{ [<field-split>subtract[3]max[0]] }}}/> (adj. from <$macrocall $name="cost-fp" value=<<field-split>>/>) 
					<% elseif [<baseskill>compare::gt[29]] %><$let baseskillexcedent={{{ [<baseskill>subtract[25]divide[5]trunc[]] }}}>
						<$macrocall $name="cost-fp" value={{{ [<field-split>subtract<baseskillexcedent>max[0]] }}}/> (adj. from <$macrocall $name="cost-fp" value=<<field-split>>/>) </$let> (adj. from <$macrocall $name="cost-fp" value=<<field-split>>/>) 
					<% else %>
						<$macrocall $name="cost-fp" value=<$macrocall $name="cost-fp" value=<<field-split>>/> 
					<% endif %>
				<% else %>
					<$macrocall $name="cost-fp" value=<$macrocall $name="cost-fp" value=<<field-split>>/>
				<% endif %>
			</$wikify>
		<% else %>
			<<field-split>> 
		<% endif %>
	</$list>
<% else %>
	Spell not in <<character>>'s Spellbook. 
<% endif %>
</$let>
\end

It all seems to work well, EXCEPT for the condition <% if [<spell>!spell-class[Blocking] -[[Reflex]]] %> that doesn’t apply anymore…which is utterly weird to me because the expected discount in fp cost works, which means this is the only condition that doesn’t work anymore and always returns true, even though it should work the same on the split elements of the field as it used to work on the whole field (it shouldn’t care about the contents it’s fed, only whether or not the spell that has said contents in the worked upon field is of spell-class “Blocking” or named “Reflex”).

I have reread your break down above of why you previously rewrote the failing condition I had to this expression, but unless I didn’t understand the break down correctly, the new situation (logic inside a list checking each individual item coming out of the split[ ]), it should still apply correctly.

I’m convinced I’m just missing something obvious, but can’t see it…

Another brackets mistake, try this:
<% if [<spell>!spell-class[Blocking]] -[[Reflex]] %>

Fred

1 Like

I knew it… I need more sleep :weary:

Thanks Fred!

Fred already spotted the issue you were asking about, but I suspect your procedure is going to break at some other points. For instance:

<$macrocall $name="cost-fp" value=<$macrocall $name="cost-fp" value=<<field-split>>/>

I suspect this is just a copy-paste error, but you can’t use a widget as the value of an attribute… and even if you could, you’d be missing a final />.

I’m also looking at that $wikify and all those conditionals and wondering whether this would be another good candidate for refactoring with functions as we did the other day. I’d want to see the cost-fp definition to make sure I understand what’s going on… but assuming all this is intended to output some plain text, functions will almost certainly be neater than wikified procedures. Let me know if this is something you want to pursue when you’re feeling better-rested!

There’s this saying in french: “ne pas avoir les yeux en face des trous”, which translates roughly to: “having eyes misaligned with their sockets”; it indeed was a copy-paste error that I overlooked.

It might very well be. Here is the definition of cost-fp:

\define cost-fp(value)
<$macrocall $name=entity icon=cost-fp value=$value$/>
\end

And entity it draws from:

\define entity(icon, label, value)
<div class="mark" title="$icon$ $value$"><span class="label entity clean"><span class="icon-$icon$">$label$</span></span><span class="data entity clean">$value$</span></div>
\end

(those were written before me by the TW I started this whole thing with, which I think predates the introduction of procedures and functions) It makes use of a base64-encoded icon font encoded in a tiddler to draw icons before $value$ that are declared in a CSS tiddler listing the character map of those icons named in the form icon-$icon$, like this example:

.icon-cost-fp:before {
  content: "\e884";
}
1 Like

I’m implementing additional logic for the modifiers given to an advantage, and running again into an issue with a two-runs conditional. The goal is to add in the select the attack modifiers, that are only usable on advantages that are considered attacks (which in my TW include have the value “yes” in their attack field) OR that have already been given the modifier called “Ranged”. Here is where I am writing that down:

\procedure select-mods(advantage, book)
<$let book={{{ [<book>!match[]] ~[<currentTiddler>] }}}>
<$select tiddler="$:/temp/generated-list-mod-state"
	default=""
	actions=<<mod-select-actions>>
>
	<option value="" disabled>Select modifier</option>
	<optgroup label="Special">
		<$list filter="[tag[GURPS Modifier]search:to-advantages:<advantage>]">
			<<mod-select-option>>
		</$list>
	</optgroup>
	<% if [<advantage>attack[yes]] [<book>search:<advantage>[Ranged]] %><optgroup label="Attack modifiers">
		<$list filter="[tag[GURPS Modifier]attack[yes]] -[[Modifier template]]">
			<<mod-select-option>>
		</$list>
	</optgroup><% endif %>
	<optgroup label="Global modifiers">
		<$list filter="[tag[GURPS Modifier]!has[to-advantages]] +[tag[GURPS Modifier]!attack[yes]] -[[Modifier template]]">
			<<mod-select-option>>
		</$list>
	</optgroup>
</$select></$let>
\end

The [<book>search:<advantage>[Ranged]] portion of the conditional in the above procedure always evaluates to true, it seems. It’s probably yet another simple oversight (in which case I’ll have to apologize again ;P). The [<advantage>attack[yes]] part of it (if alone) works.

This is the same issue you were running into here:

Your major hint that [<book>search:<advantage>[Ranged]] isn’t valid syntax is the >[ — if you ever see two types of brackets in a row (i.e. without a filter operator between them) it’s generally a sign that something has gone wrong! In this case, since search:<advantage> isn’t valid syntax, the filter ignores it and replaces all the previous content of the filter run with the literal string Ranged. The filter run becomes effectively

<% if [<advantage>attack[yes]] [[Ranged]] %>

and since the [[Ranged]] run is always true (because you’re not testing it for anything), the conditional as a whole is also true.

Try replacing the conditional with a $list as I showed you last time. As a slight modification, you may also want to use the ~/:else filter run prefix before the second run. This would ensure that the second filter run is only used if the first filter run does not return any results, so it would make your filter slightly more efficient. :slight_smile:

1 Like

Riiiight.

Apologies :stuck_out_tongue_winking_eye:

So for anybody else stumbling on this in our future, I changed it to this:

<$list filter=`[<advantage>attack[yes]] ~[<book>search:$(advantage$)[Ranged]]`><optgroup label="Attack modifiers">
		<$list filter="[tag[GURPS Modifier]attack[yes]] -[[Modifier template]]">
			<<mod-select-option>>
		</$list>
	</optgroup></$list>

… which now works.

Hey, it’s me again :stuck_out_tongue_winking_eye:

Now, I’m implementing minimal character points (CP) costs to advantages; certain advantages cannot cost less than that minimal cost even when they have modifiers that would bring them below otherwise. This is usually taken care of by the fact that no amount of modifiers can result in a bigger discount that -80% (which I implemented in the lvl-mods-sum-pure procedure through a simple max[-80]), but there is this pesky advantage called “Magery” that has a base cost of 5 for its level 0, then 10 per additional level, and only the level 0 cannot be discounted at all through modifiers, whereas levels above 0 can be. In other words, modifiers should not apply discounts to Magery level 0, but should discount the cost of Magery level 1+. In order to account for this specific rule, I added a field called min-cp to advantages, which is 5 in the case of Magery; that way, I know Magery’s minimal total cost cannot be lower than 5, but I can still discount its costs through modifiers regardless of its level.

In any case, this time, I spotted the situation I overlooked twice before of not being able to use variables as an operator suffix, and it seems to work well:

\procedure calc-adv-cp(advantage, book)
<$let
 advbasecost={{{ [<advantage>get[advantage-base-cost]] }}}
 advmincost={{{ [<advantage>has[min-cp]get[min-cp]] ~0 }}}
 advlvlcost={{{ [<advantage>get[advantage-level-cost]] }}}
 advlvl={{{ [<book>getindex<advantage>] ~0 }}}
 advextracost={{{ [<advlvl>multiply<advlvlcost>] }}}
 advpremodtotal={{{ [<advbasecost>add<advextracost>] }}}
>
<% if [<book>has<advantage>] %>
<$wikify name="modvalue" text="<$macrocall $name='lvl-mods-sum-pure' advbook=<<book>> adv=<<advantage>>/>">
<$let advpostmodal={{{ [<advpremodtotal>multiply<modvalue>divide[100]ceil[]] +[add<advpremodtotal>] }}}>
<$list filter=`[<advpostmodal>compare::gt[$(advmincost)$]] ~[<advmincost>]` variable="minimalcost"><$text text={{{ [<minimalcost>] }}} /><% if [<minimalcost>!match<advpremodal>] %> (adj. from <<advpremodtotal>>)<% endif %></$list>
</$let></$wikify>
<% else %>
<$text text=<<advpremodtotal>> />
<% endif %>
</$let>
\end

However, this particular line’s conditional:

<$list filter=`[<advpostmodal>compare::gt[$(advmincost)$]] ~[<advmincost>]` variable="minimalcost"><$text text={{{ [<minimalcost>] }}} /><% if [<minimalcost>!match<advpremodal>] %> (adj. from <<advpremodtotal>>)<% endif %></$list>

… gets rendered when it should not, in the case of Magery:
image

Clearly, 5 (<minimalcost>) matches 5 (<advpremodal>), yet it still shows the “(adj. from 5)” of the conditional <% if [<minimalcost>!match[<advpremodal>]] %> next to the <$text text={{{ [<minimalcost>] }}} />. So I’m puzzled :stuck_out_tongue_winking_eye:

Sorry, I didn’t try to understand your full use case, but spotted this syntax error:

which should read:
<% if [<minimalcost>!match<advpremodal>] %>

Hope this helps

Fred

Good catch (shame! shame! shame on me!); however, the problem of the conditional evaluating to true remains :face_with_monocle:

Well, searching your code I can’t find advpremodal definition, should it be advpremodtotal?

Fred

1 Like

I think I’m getting stupider by the minute.
shame-bell

I will also note that in this filter snippet…

<advpostmodal>compare::gt[$(advmincost)$]

you can and probably should use

<advpostmodal>compare::gt<advmincost>

instead. I know I’ve just been telling you not to use <variables> as filter operators, but <advmincost> is a filter parameter — this is how they’re designed to work! As an illustration of the difference:

search:$(field)$[value] → $(field)$ is a suffix modifying the search operator = no <brackets>
search:advantage<value> → <value> is a variable parameter of the search operator = use with angle brackets in place of [a literal value]

Obviously your code works as written, too, but this should make it slightly easier to read. :slight_smile:

2 Likes