How to use an if statement to set the value of a datamap entry based on the value of a different entry and/or change an entry in a dm to true/false

Hello,

I am trying to learn the more in-depth parts of Harlowe 3.3.8, and I am using the manual as a jumping off point. Essentially taking the examples in the manual and trying to figure out how they work by messing around with them (i.e. breaking them) and/or adding new elements. I am very much not programming minded, I struggle with figuring out how to translate snippets into implementation, in that regard the manual is both a blessing and a curse, I wouldn’t mind if almost every example was expanded upon and snippets given more context. I say this to ask if you do answer, please be patient with me, and I am sorry for how much hand-holding I require.

Anyway, sorry, in the manual under (Macro:) is the following example:

(set: $charSheet to (dm: "name", str, "HP", num, "poison", num, "heartbreak", bool))
(set: $healthSummary to (macro: $charSheet-type _stats, [
    This text inside the macro is not displayed during the game.
    (set: _TheyAre to _stats's name + " is ")
    Dead characters get a single, pithy line.
    (if: _stats's HP <= 0)[(output: _TheyAre + "deceased.")]
    Living characters get specific status conditions referred to.
    (output-data:
        _TheyAre + "in " + (cond: _stats's HP > 50, "fair", "poor") + " health." +
        (cond: _stats's poison > 0, " " + _TheyAre + "poisoned.", "") +
        (cond: _stats's heartbreak, " " + _TheyAre + "heartbroken.", "")
    )
]))
(set: $steelyStats to (dm: "name", "Steely", "HP", 80, "poison", 0, "heartbreak", true))
($healthSummary: $steelyStats)

which btw, if the first output should be changed to output-data, as if Steely dies in this example, an error is returned instead of the “deceased” if ‘HP’ reaches 0.

And this is in my startup passage:

(set: $steelyStats to (dm: "name", "Steely", "HP", 80, "poison", 0, "heartbreak", true))

To test how to change stats, I made the following links back to this same sheet but with set macros attached

(link: "gain 40 health")[(set: $steelyStats to (dm-altered: via its value + 40 where its name is 'HP', $steelyStats))[(go-to:"Character Sheet")]]
(link: "lose 40 health")[(set: $steelyStats to (dm-altered: via its value - 40 where its name is 'HP', $steelyStats))[(go-to:"Character Sheet")]]
(link: "gain poison")[(set: $steelyStats to (dm-altered: via its value - 5 where its name is 'poison', $steelyStats))[(go-to:"Character Sheet")]]
(link: "lose poison")[(set: $steelyStats to (dm-altered: via its value + 5 where its name is 'poison', $steelyStats))[(go-to:"Character Sheet")]]

I am wanting to do one of two things, preferably I would like to learn how to do both.

  1. if ‘poison’ is - 5, then when I click the link or upon re-entry to the passage, I would like ‘HP’ to decrease by 5, if ‘poison’ is - 10 decrease ‘HP’ by 10
  • Along with this one, I would like to learn how to set a maximum, because you can only be so poisoned, so there shouldn’t be the ability to go above 0 poison.

What I have written up myself so far but it doesn’t work (I know part of the reason is that there’s nothing after value but I don’t have the foggiest what else to put there and achieve my goal):

(if: $steelyStats's (dm-names: "poison") is > 0)[(set: $steelyStats to (dm-altered: where its name is 'poison' via its value where its name is 'HP'))]

and/or

  1. How to use a lambda to set a boolean. In a different iteration, I attempted to set poison to a boolean, but found I couldn’t figure out how to use the (dm-altered:) to change the value to a ‘true’ for poisoned or a ‘false’ for not.
  • For this iteration, I would like a set value to be removed from ‘HP’ every time a turn is taken and the ‘poison’ is ‘true’.

I attempted to do change the boolean of heartbreak with this code:

(link: "find a new love")[(set: $steelyStats's (dm-names:'heartbreak') to false)[(go-to:"Character Sheet")]]

But if I click it, Steely is still ‘heartbroken’ and I get an error that states:

A datamap (with “name”, “HP”, “poison”, and “heartbreak”) can only have string data names, not an empty variable.

To summarize:

  • How do I check the value of a datamap entry (e.g. ‘poison’) and if 1) it is not zero then change another datamap entry’s (e.g. ‘HP’) value by the same amount or 2) it is ‘true’ then change another datamap entry’s value by a set amount
  • How can I change the value of a datamap entry when it is a boolean

There are some “interesting” things about your original examples that I will discuss later, but first to your questions about how to access a specific property of a DataMap, and how to assign a value to that specific property.

note: The Datamap data section Harlowes manual covers both of the following syntax examples.

If we use your own Datamap as a starting point…

(set: $steelyStats to (dm: "name", "Steely", "HP", 80, "poison", 0, "heartbreak", true))

1: Syntaxes for accessing the 'poison' property of the $steelyStats DataMap

Poison: (print: $steelyStats's 'poison' )
or
Poison: (print: 'poison' of $steelyStats )

note: if the Name of the property is stored in a variable, or determined by an expression of some kind, then open & close parenthesis ( ) should be wrapped around the variable/expression.

(set: _property to 'poison')

Poison: (print: $steelyStats's (_property) )

2a: Syntax for assigning a value to the ‘poison’ property of the $steelyStats DataMap

(set: $steelyStats's 'poison' to 10)

note: again parenthesis should be used is property name is stored in a variable or determined by an expression.

(set: $steelyStats's (_property) to 10)

2b: Syntax for incrementing / decrementing the value of the ‘poison’ property of the $steelyStats DataMap

(set: $steelyStats's 'poison' to it + 10)
or
(set: $steelyStats's 'poison' to it - 5)

Other things:
1: The purpose of the (output:) macro is to display the content generated by its associated Hook on the page. The purpose of the (output-data:) macro is to define the value the custom macro will return.

2: The reason the value being returned by your custom macro is being displayed in the page, is because you’re not assigning that value to a variable. So Harlowe’s Passage Content Parser is treating that returned value as if it is part of the passage’s content and displaying it.

So if the purpose of your custom macro is to display content then the (output:) macro should of been used.
eg

(set: $healthSummary to (macro: $charSheet-type _stats, [
	(set: _TheyAre to _stats's name + " is ")
	(if: _stats's HP <= 0)[
		(output:)[_TheyAre + "deceased."]
	]
	(else:)[
		(set: _message to _TheyAre + "in "
		+ (cond: _stats's HP > 50, "fair", "poor") + " health."
		+ (cond: _stats's poison > 0, " " + _TheyAre + "poisoned.", "")
		+ (cond: _stats's heartbreak, " " + _TheyAre + "heartbroken.", ""))
		(output:)[_message]
	]
]))

note: due to how Harlowe’s Passage Transition works, the content of the (output:) macro’s associated Hook and the argument passed to the (output-data:) macro may be processed twice. So the generally advice is to do the bare minimum calculation or conditional logic within that Hook or argument. Which is why the above determines the message to output before calling the 2nd (output:) macro.

3: The (dm-altered:) macro used in your “Transition Link with Setter” examples doesn’t need or support an associated Hook, so there is no need to wrap the (go-to:) macro call in Square Brackets [ ].

note: The correct macro to use when creating a “Transition Link with Setter” is (link-reveal-goto:).

(link-reveal-goto: "gain 40 health", "Character Sheet")[
    (set: $steelyStats's 'HP' to it + 40)
]

4: You asked “how to set a maximum”.

Numerical values generally don’t have an inbuilt way to associate a range limit (eg; minimum and/or maximum) to them, that is generally done at the point of assignment using one of many “limit” or “clamp” techniques. Harlowe doesn’t include a “clamp” related macro, but it does have “limit” related macros like (min:) and (max:) that can be used, and these macros can be combined to perform a “clamp” if needed.

So to stop a numerical value from increasing past zero…

(set: $steelyStats's 'poison' to (min: it + 10, 0))

…or from decreasing below zero…

(set: $steelyStats's 'poison' to (max: it - 10, 0))

5: You asked “How can I change the value of a datamap entry when it is a boolean”. So based on the above 2a syntax example, the answer would be…

(set: $steelyStats's "heartbreak" to false) 

note: while using the (dm-altered:) macro to make such a change is not an efficient way to do the above Boolean assignment, for completeness sake I will include an example of how such might be achieved.

(set: $steelyStats to (dm-altered: _property via false where _property's name is "heartbreak", $steelyStats))
2 Likes

I wasn’t able to post due to my internet going out, I apologize for the late response. Luckily I had twinery downloaded and was able to continue to play around and I’m so embarrassed that the issue was so simply solved even moments after my initial post. Just as you pointed out in 2b, simply calling the datamap using it’s variable name was enough to make change happen.
(set: $steelyStats's 'HP' to it + 40)[(go-to:"Character Sheet")]

Other things:

  1. For the output issue I pointed out, in the character sheet, if the first ‘output’ isn’t changed to ‘output-data’ then an error is produced if HP hits 0. No “Steely is deceased”. When I change it to ‘output-data’, the HP getting to 0 is properly changed to “Steely is deceased”. The error message is just “1 error occured when running custom macro”, so I’m not sure what the exact issue is, but it looks like it pulls the whole datamap as a ‘-type’ and doesn’t like that.

  2. I am not having an issue with the macro being displayed, thank you for the information though.

  3. As far as having the HP change with the poison, I did figure this out on my own as well, but instead of on the link, I put it in the passage, so when going to the next passage, I used an if statement
    (if: $steelyStats's 'poison' is < 0)[(set: $steelyStats's 'HP' to it + $steelyStats's 'poison')'

  4. Your solution is much more elegant than mine lol. I had it so if a player moved passages with an amount of poison bigger than 0 (since I made poison a negative) it’s just set back to 0 lol.
    (if: $steelyStats's 'poison' is > 0)[(set: $steelyStats's 'poison' to 0)]

I will probably look at changing what I have to be what you’ve provided, it appears a much better fail safe than to just constantly check if the stat has gone above 0 and setting it back to 0. My method is just begging for a single instance to be overlooked and cause issues lol.

  1. Thanks so much for your completeness, I’m truly grateful for it. I wish I was better at intuitively connecting ideas/extrapolating the correct way to format things, but I do need an example of what works to learn, and I appreciate it so much.

Like the (dm-altered:) macro I mentioned earlier, the (set:) macro also doesn’t need or support an associated Hook, so again the above (go-to:) macro call shouldn’t be wrapped in square brackets…

(set: $steelyStats's 'HP' to it + 40)(go-to: "Character Sheet")

re: For the output issue I pointed out, in the character sheet

Without examples of your latest custom macro definition, and of how it’s being used, it is difficult to debug the issue.

I am not having an issue with the macro being displayed, thank you for the information though.

The multiple execution of a custom macro’s “output” during a single Passage Transition behaviour I described doesn’t stop that “output” being displayed or returned. It just potentially results in the custom macro having to do the same amount of effort twice per call, which is inefficient and in some cases can cause unexpected outcomes. Which is why I advised not performing calculations or conditional logic within the “output” generation/determination phase of a custom macro’s definition.

it’s just set back to 0 lol.
(if: $steelyStats's 'poison' is > 0)[(set: $steelyStats's 'poison' to 0)]

You shouldn’t use the is “equality” keyword operator in conjunction with any of the mathematical comparison operators.

A break down of the above conditional expression:

  • the is operator represents “exactly equal to”
  • the > operator represents “greater than”
  • the current value of ‘poison’ can never be both “exactly equal to” and “greater than” zero at the same time.

The reason why your $steelyStats's 'poison' is > 0 conditional expression isn’t causing an error, is because Harlowe 3.x was altered to ignore this specific type of error in this specific situation.

The correct way to express what you’re trying to achieve is…

(if: $steelyStats's 'poison' > 0)[(set: $steelyStats's 'poison' to 0)]
If you take the (macro:) example directly from the Harlowe 3.3.8 manual, which is 
(set: $charSheet to (dm: "name", str, "HP", num, "poison", num, "heartbreak", bool))
(set: $healthSummary to (macro: $charSheet-type _stats, [
    This text inside the macro is not displayed during the game.
    (set: _TheyAre to _stats's name + " is ")
    Dead characters get a single, pithy line.
    (if: _stats's HP <= 0)[(output: _TheyAre + "deceased.")]
    Living characters get specific status conditions referred to.
    (output-data:
        _TheyAre + "in " + (cond: _stats's HP > 50, "fair", "poor") + " health." +
        (cond: _stats's poison > 0, " " + _TheyAre + "poisoned.", "") +
        (cond: _stats's heartbreak, " " + _TheyAre + "heartbroken.", "")
    )
]))
(set: $steelyStats to (dm: "name", "Steely", "HP", 80, "poison", 0, "heartbreak", true))
($healthSummary: $steelyStats)

Anything that would cause ‘HP’ to = 0 will result in an error.

It’s not my custom macro, I’m simply using it as a means to learn how to harlowe better. I did change the first “output” to “output-data” in my own macro and found that it works, anything that would set ‘HP’ to 0 or below will result in the intended “Steely is deceased”.

Greyelf:

You shouldn’t use the is “equality” keyword operator in conjunction with any of the mathematical comparison operators.

ah, my bad, I was thinking of it like English, didn’t consider that ‘is’ is ‘=’. Thank you for pointing that out.

If you take the (macro:) example directly from the Harlowe 3.3.8 manual

One of the issues with the Harlowe manual, is that the story format’s developer doesn’t always test their examples. Or re-test them when a feature changes, or a new feature is added.

As shown in the documentation of the (output:) macro itself:

  • the macro doesn’t accept any arguments.
  • the macro needs an associated Hook.

eg. the syntax of a (output:) macro call is…

(output:)[...stuff to output...]

However, even if we fix that syntax error in that example, to be something like…

(output:)[ _TheyAre + "deceased."]

…the output won’t be the concatenation of variable’s value and the “deceased” String literal, for that concatenation to occur the expression would need to be placed in a (print:) macro…

(output:)[(print: _TheyAre + "deceased.")]

note: this is another reason for determining the outputs within the custom macro definition’s body before calling the (output:) macro.
eg.

(set: _message to _TheyAre + "deceased.")
(output:)[_message]

And the same concatenation issue is likely why the example you referenced is later using (output-data:) instead of (output:). But the purpose of the (output-data:) macro is to return a value that will be assigned to a variable, not to return a value that will be directly injected into the page’s output, which is what the (output:) macro does.