Achieve with Reference

Does anyone know if there is a way to use the achieve command with references?
For context, I have an “easy mode” option that allows players to retry combat if they die, but disables achievements for the rest of that playthrough.
I was hoping to pipe it through a subroutine to make life easier, but I get an error message.

*label award_achievement
*params achievement_id
*if ((easy_mode) and not(player_alive))
    *comment no achievement
*else
    *achieve {achievement_id}
*return

image

I’ve put all kinds of utils like increasing numeric stats inside of a “utils” scene for similar types of things.
For example, I have a “Notify Stats” option which is used in my increase stats subroutine, if the player sets it to false, they don’t see the stat changes.

*label increase_stat
*params stat_id stat_increase stat_name
*set {stat_id} +{stat_increase}
*if (stat_name = "")
    *set stat_name stat_id
*if (notify_stats)
    $!{stat_name} +${stat_increase}
    *line_break
*return

It appears you haven’t defined your achievements properly. If I understand correctly, *params create temporary variables, so in your second subroutine, *params create temporary vars stat_id and stat_increase, sets them based on the data in *gosub command and the subroutine works properly. In your first subroutine, *params creates a temporary achievement_id variable, but achievements by their nature aren’t temporary variables, they have to be declared in startup file, so *achieve command can’t use that temp variable achievement_id, instead goes to the startup to find an achievement called achievement_id, and fails.

You should declare achievements individually and deal with them as they appear in the plot. If a player character can die often, you can create a subroutine to revive them and send them to the fight again.

*if health <= 0
  Everything goes dark.
  *gosub check_revival
  *if player_alive
    On the brink of death, you find your inner strength and continue to fight the dastardly reptile.
    *goto dragon_fight
  *else
    You've died from a terminal case of being squashed by a dragon.
    *ending
*elseif dragon_health <= 0
  Congratulations, you've defeated the dragon!
  *if easy_mode = false
    *achieve defeat_dragon
  *goto victory
*else
  You are still fighting the dragon.
  *goto fight_dragon

*label check_revival
*if easy_mode
  *set health 100
  *set player_alive true
  *return
*else
  Do you wish to switch to easy mode to continue the fight (disables achievements for the rest of the game)?
    *choice
      #Yes, revive me and let me retry!
        *set health 100
        *set player_alive true
        *set easy_mode true
        *return
      #No, I accept my fate!
        So be it.
        *set player_alive false
        *return

That’s if you want the player to be able to switch to the easy difficulty later in the game. If you only allow the player to choose difficulty mode in the beginning, the subroutine for revival will be different. Just don’t use *params command to define achievements.

ChoiceScript is quirky on the edges and inconsistent in its design. What you are trying to do is unfortunately impossible. I can think of two workarounds.

The simplest, but it uses the *script command:

*label award_achievement
*params achievement_id
*if (not(easy_mode) and player_alive)
    *script window.stats.scene.achieve(window.stats.scene.temps.achievement_id)
*return

Note that I inverted the conditional to make it simpler.


The second solution is to have a utility scene only for handling achievements. You use the reference id in the *goto command instead of the *achieve command. Unfortunately, you’ll have to write a label for each achievement.

== startup.txt ==

*achievement diplomatic visible 100 Peacekeeper
    Resolve at least 5 conflicts through diplomatic and non-violent means to earn this achievement.
    At times, a word can be mightier than a sword.
== utils_achieve.txt ==

*label award_achievement
*params achievement_id
*if (not(easy_mode) and player_alive)
    *goto {achievement_id}
*return

*label diplomatic
*achieve diplomatic
*return

I did actually declare them in the startup, the achievement_id is just used to pass the id of the achievement through.
If you look at the increase_stat subroutine, I use the same method to pass through the id of a variable, then use the set command to increase it’s value based on the id provided.

I figured I could do it with script, but I’m trying to avoid using it. Part of the fun of learning a new programming language to me is figuring out what I can do with it, and what the limitations are.

I was hoping to avoid typing them all out. (Cause I’m lazy.)
But I guess I’ll have to do something like this:

*label award_achievement
*params achievement_id
*if ((easy_mode) and not(player_alive))
    *return
*if (achievement_id = "dragonslayer")
    *achieve dragonslayer
*elseif (achievement_id = "druid")
    *achieve druid
*elseif (achievement_id = "merchantile")
    *achieve merchantile
*else
    *comment invalid achievement
*return

It will just return immediately if they are on easy mode and are not alive (died), but otherwise continue and award the achievement.
Another nice feature would be a switch-case block, but that’s also my laziness talking.

Yes, that’s better than my suggestion. :grin: Plenty of things missing from ChoiceScript, like a simple loop syntax. :man_shrugging:

I was wrong then. Anyway, it appears *achieve command tries to work with {achieve_id} achievement insead of whatever value assigned to achievement_id variable.

However, I don’t see how

*gosub award_achievement diplomacy

*label diplomacy
Long subroutine with achievements.

is shorter and easier than

*if not easy_mode
  *achieve diplomacy

Loops would certainly be nice, but at least you can code them relatively easily with something like this:

*label loop_start
*temp count 0
*label loop_body
*if (count < 10)
    Looped.
    *set count +1
    *goto loop_body
*else
    Finished looping.
    *return

I’d wager making a pull request on the repo in github is unlikely to lead anywhere?

The main reason for using the util subroutine is that I don’t need to copy paste the if condition every time.
The reason for not wanting to do that is what if I decide to change the condition?
All of a sudden I need to find every place where I used it and update the code.
By using a subroutine, I only update it in one place and everything fixes itself.

2 Likes