Author Topic: Scripting basics  (Read 1586 times)

Offline Kzer-Za

  • Colonel
  • ****
  • Posts: 140
    • View Profile
Scripting basics
« on: August 15, 2019, 02:04:14 pm »
I have some questions concerning the script examples form here: https://openxcom.org/forum/index.php?topic=5245.0

Do I get it right, that
1. the engine checks every frame whether any scripts should be executed,
2. if more than one script shoud be executed in this frame, then "offset" defines in which order they are executed?

Concerning the script "Hit confirm by red "flash":"

1. Scripts "damageUnit" and "recolorUnitSprite" have the same offset. How does it work then? I would have thought (and would be wrong, since this script works) that I need to set the offset of recolorUnitSprite to 11.

2. According to my experiments, damageUnit is actually triggered not when the unit is damaged, but when it is hit. If the projectile or a melee hit does not miss, this script is triggered, even if the damage inflicted is 0 because of armor. When is hitUnit script triggered then? I tried changing in this example damageUnit to hitUnit, and it stopped working, i.e. the unit stopped flashing red when hit.

Also, do I get it right that newTurnUnit is triggered in the beginning of every turn, and createUnit is triggered when a new unit is created, which would in most cases mean, in the beginning of the battle?
« Last Edit: August 15, 2019, 02:14:05 pm by Kzer-Za »

Offline Kzer-Za

  • Colonel
  • ****
  • Posts: 140
    • View Profile
Re: Scripting basics
« Reply #1 on: August 16, 2019, 01:52:14 pm »
Okay, I'm flying blind here, but I'm gonna try to make Celatids clone themselves.

This is the code that I'm trying to make working.

Code: [Select]
extended:
  tags:
    RuleItem:
      TIMER: int

  scripts:

    newTurnItem:
      - offset: 1
        code: |
          var int timer;
         
          item.getTag timer Tag.TIMER;
          debug_log 1 timer;
          if neq timer 0;
            item.setFuseTimer timer;
            sub timer 1;
            item.setTag Tag.TIMER timer;
          end;
          return;

items:
# "Grenades" from which young Celatids come
  - &STR_CELATID_GEMMA
    type: STR_CELATID_GEMMA
    size: 0.1
    weight: 0
    power: 0
    damageType: 0
    battleType: 4
    blastRadius: 0
    fixedWeapon: true
    armor: 1
    fuseType: -1
    isExplodingInHands: true
    spawnUnit: STR_CELATID_YOUNG
    spawnUnitFaction: 1

  - type: STR_CELATID_GEMMA1
    refNode: *STR_CELATID_GEMMA
    tags:
      TIMER: 3

  - type: STR_CELATID_GEMMA2
    refNode: *STR_CELATID_GEMMA
    tags:
      TIMER: 6

  - type: STR_CELATID_GEMMA3
    refNode: *STR_CELATID_GEMMA
    tags:
      TIMER: 9

  - type: STR_CELATID_GEMMA4
    refNode: *STR_CELATID_GEMMA
    tags:
      TIMER: 12

  - type: STR_CELATID_GEMMA5
    refNode: *STR_CELATID_GEMMA
    tags:
      TIMER: 15

  - type: STR_CELATID_GEMMA6
    refNode: *STR_CELATID_GEMMA
    tags:
      TIMER: 18

But I get this error:

Code: [Select]
[16-08-2019_11-01-14] [ERROR] Can't match overload for operator 'BattleItem.getTag' for:
[16-08-2019_11-01-14] [ERROR]   [ptre BattleItem] [var int] [RuleItem.Tag]
[16-08-2019_11-01-14] [ERROR] Excepted:
[16-08-2019_11-01-14] [ERROR]   [ptr BattleItem] [var int] [BattleItem.Tag]
[16-08-2019_11-01-14] [ERROR] Error in parsing script 'newTurnItem' for 'Global Event Script': invalid operation in line: 'item.getTag timer Tag.TIMER;'

Why isn't it working? Judging by the examples, it seems that the syntax is right.

Offline Yankes

  • Moderator
  • Commander
  • ***
  • Posts: 2141
    • View Profile
Re: Scripting basics
« Reply #2 on: August 16, 2019, 03:53:09 pm »
Read error message:
Code: [Select]
Can't match overload for operator 'BattleItem.getTag' for:
  [ptre BattleItem] [var int] [RuleItem.Tag]
Excepted:
  [ptr BattleItem] [var int] [BattleItem.Tag]
You passed tag of type `RuleItem.Tag` to function that expect type `BattleItem.Tag`.

Each tag have unique type than is connected to some type from game.
One type is `RuleItem`, this mean configuration of item. Another type is `BattleItem`, this is item it self.

You defined tag that belong to ruleset of item:
Code: [Select]
  tags:
    RuleItem:
      TIMER: int
but in script you try useing it as it would belong to item:

Code: [Select]
          item.getTag timer Tag.TIMER;

You need do somting like this:
Code: [Select]
          var ptr RuleItem rule;
          ...
          item.getRule rule;
          rule.getTag timer Tag.TIMER;

btw what is point of
Code: [Select]
            item.setTag Tag.TIMER timer;
?

Offline Yankes

  • Moderator
  • Commander
  • ***
  • Posts: 2141
    • View Profile
Re: Scripting basics
« Reply #3 on: August 16, 2019, 04:36:41 pm »
I have some questions concerning the script examples form here: https://openxcom.org/forum/index.php?topic=5245.0

Do I get it right, that
1. the engine checks every frame whether any scripts should be executed,
2. if more than one script shoud be executed in this frame, then "offset" defines in which order they are executed?

Concerning the script "Hit confirm by red "flash":"

1. Scripts "damageUnit" and "recolorUnitSprite" have the same offset. How does it work then? I would have thought (and would be wrong, since this script works) that I need to set the offset of recolorUnitSprite to 11.

2. According to my experiments, damageUnit is actually triggered not when the unit is damaged, but when it is hit. If the projectile or a melee hit does not miss, this script is triggered, even if the damage inflicted is 0 because of armor. When is hitUnit script triggered then? I tried changing in this example damageUnit to hitUnit, and it stopped working, i.e. the unit stopped flashing red when hit.

Also, do I get it right that newTurnUnit is triggered in the beginning of every turn, and createUnit is triggered when a new unit is created, which would in most cases mean, in the beginning of the battle?
First part:
1. This depend on script type, every script have specific situation in game when they are called. Scripts that alter graphic need per definition to be called for each frame, other like "nextTurn" or "hit" are called only in specific situations (and this is completely independent from animation frames).
2. "offset" is used to order them per script type. This is mainly for integration between multiple mods that need run script that interact with each others. You can have always `1` but preferred is that you some random value between `0.01` to `99.99` (or in spacial cases from`-99.99` to `-0.01`) for other moddes to have chance to adjust to your scripts.

Second part:
1. "offset" from other script types do not affect other scripts types.
2. `damageUnit` and `hitUnit` are indeed always both call when unit is hit with damage, if unit is "hit" with 0 power then no script is run. then why there is two functions? because they do different job:

hitUnit: can adjust damage and place where damage is done (body part and armor part). Run before `damageUnit`.
damageUnit: is used to alter finnal change of stats of unit when its hit by weapon (this could have 0 power because armor/`hitUnit` script reduced some damage before).

Probably you make some error in `hitUnit` that it do not work as expected.

Offline Kzer-Za

  • Colonel
  • ****
  • Posts: 140
    • View Profile
Re: Scripting basics
« Reply #4 on: August 16, 2019, 05:32:36 pm »
Thank you a ton for the explanations! It becomes much clearer.

And thanks for the help with Celatid! I had to replace getRule with getRuleItem (looked into your scripts from Piratez :)), but after that it worked :)

I also had to comment out the line you asked about

Code: [Select]
item.setTag Tag.TIMER timer;
because it also caused an error. Using it was a mistake on my part from the beginning, but the idea was to adjust the timer, since in my code the FuseTimer is set every turn from the tag, so I thought I would write the adjusted timer value back into the tag, or else the timer each turn would be reset to X turns, so the grenade would never go off.

I already realized that I should just prime these grenades not in newTurnItem but in createItem, I made the change, and it works.

However, I still need a newTurnItem for these grenades, to check every turn whether they are still "held" by the Celatid, or it was killed (since the Celatids are supposed to clone themselves only while they are alive). I suppose, I need to check whether "item.getOwner" is null?

Offline Kzer-Za

  • Colonel
  • ****
  • Posts: 140
    • View Profile
Re: Scripting basics
« Reply #5 on: August 16, 2019, 06:28:56 pm »
Here's the code with which I'm trying to unprime the Celatid gemmae not held by a Celatid:

Code: [Select]
    newTurnItem:
      - offset: 41
        code: |
          var ptr RuleItem itemRuleset;
          var ptre BattleUnit itemOwner;
          var str itemName;
         
          item.getOwner itemOwner;
          item.getRuleItem itemRuleset;
          itemRuleset.getName itemName;
         
          # prevent unpriming grenades lying on the ground, that are not gemmae
          if neq itemName CELATID_GEMMA;
            return;
          end;
         
          if eq itemOwner null;
            item.setFuseTimer -1;
          end;
          return;

However, I get this error (and a lot more down the log, but I think they are conncted to this one):

Code: [Select]
[16-08-2019_18-19-58] [ERROR] Invalid type 'str'
I tried "var string", but the "string" is invalid too. Could you please tell, how do I specify strings?

Offline Yankes

  • Moderator
  • Commander
  • ***
  • Posts: 2141
    • View Profile
Re: Scripting basics
« Reply #6 on: August 16, 2019, 07:13:50 pm »
Right now text is not supported in script engines, as scripts are usually limited only to some low level operation there was no need for handling strings to implement anything. Probably when I will starting implementing unit creation from script then I will need add string support (or tags will be able to store other values than ints).

Your current problem could be solved other way, you add to `RuleItem` new tag that represents "CELATID_GEMMA" and if some item rule have it then its celatid gemma.

Offline Kzer-Za

  • Colonel
  • ****
  • Posts: 140
    • View Profile
Re: Scripting basics
« Reply #7 on: August 16, 2019, 10:23:13 pm »
Thanks, that works!

May I ask one more question? Is there a way to trigger a script when a unit dies and/or when a non-zero damage gets thrrough the armor?

I want to know because, first, it would help me iron out my cloning Celatids: right now, even though the code that unprimes the gemmae lying on the ground works, but if the Celatid dies during the turn right before the gemma would spawn a clone, then the next turn it spawns anyway. So I was thinking of checking the moment the Celatid dies and right then removing the gammae somehow (though, I would need to think of a way).

Second, it would be useful in general to be able to trigger an event when a unit is actually hurt. Since hitUnit and damageUnit are used to alter the damage done, they are triggered before the health is affected. I thought of comparing the armor with the damage of the item that deals the damage, but that would not take into account how the damage is randomized. So, could I check for whether the damage got through the armor?

Offline Yankes

  • Moderator
  • Commander
  • ***
  • Posts: 2141
    • View Profile
Re: Scripting basics
« Reply #8 on: August 16, 2019, 11:20:00 pm »
`damageUnit` IS place where you check if unit survive hit, there is variable `toHealth` that store value that directly will be applied to health of unit.
if current health is less than `toHealth` will die until some other script will not alter this value.
This mean you script need be run last otherwise you will get wrong result. You should set `offset: 99` to have confidence that nobody unintendedly run theirs script after yours.

Other place where unit can die is next turn event. It is called after all starts are changed and you can check if anyone die.

Offline Kzer-Za

  • Colonel
  • ****
  • Posts: 140
    • View Profile
Re: Scripting basics
« Reply #9 on: August 17, 2019, 12:50:17 pm »
Ah, thanks! At first I got an error, but then (thanks again to your scripts from Piratez, it's great help) changed "toHealth" to "to_health" and got it.

Now I apologize for so many questions, but I have more :)

I suppose that to get the list of items carried by the unit I need to use "unit.getInventory"? But what type of variable is it? Each separate item, as far as I understand, is "ptre", but how do I address that list? And are there cycles in that language? I thought of doing something like:

Code: [Select]
  scripts:
    damageUnit:
      - offset: 41
        code: |
          var ??? itemList;
         
          debug_log debug;
         
          if lt unit.getHealth to_health;
            for i in itemList; # pseudocode, since I don't know the right syntax yet
              item.setFuseTimer -1;
            end;
          end;
         
          return;

Also, how do I use these debug messages? I thought that all I needed to do was put somewhere in the script "debug_log somemessage", and I would find "somemessage" in openxcom.log. But I tried it (like in the code above, but in a script that I know is working ), and can't find them in openxcom.log. What am I doing wrong?

Offline Yankes

  • Moderator
  • Commander
  • ***
  • Posts: 2141
    • View Profile
Re: Scripting basics
« Reply #10 on: August 17, 2019, 05:41:19 pm »
`ptre` and `ptr` mean pointers, as script engine is close conected to C++ I expose in it some basic concepts from the language.

ptre - this is read only pointer to some object from game, you can only read values from it and do not change any thing.
ptr - this is pointer to some game object. you can change any thing in it as long there is function that allow this.

For list, this is one thing that my language is lacking, this is again up to this point I could work around this and there was no need to add this.
This is on my TODO list.
As list are not available loops are still not added.

Overall I think when I finish ufo dedection, I will look see if I can easy add this missing things like loops, strings and lists. Probably userbase grown enough that now is time to finish this.

For debug, this is very simple system, you give it list of variables/values and they are output to log,
What work for `add x 10;` will work for `debug_log x 10;` because both are handed exactly same way. You can use `debug_log item;` to show some more info about object. When I add string then `debug_log "Test test";` will work.

Offline Kzer-Za

  • Colonel
  • ****
  • Posts: 140
    • View Profile
Re: Scripting basics
« Reply #11 on: August 17, 2019, 11:02:56 pm »
Then how can I get these 6 "grenades" (gemmae) from the damaged Celatid inventory to unprime them? I see in your scripts things like "attackingUnit.getLeftHandWeapon", but what is the command (is there such a command?) to get an item from ... I don't know, where would the grenades be by default?

Also, I'm sorry, but I still don't get the usage of debug mesages. I tried now modifying the script that confirms the hit by a red flash from here: https://openxcom.org/forum/index.php?topic=5245.0

I just added two lines:

Code: [Select]
          debug_log 10 temp;
          debug_log 20 to_health;

as follows:

Code: [Select]
    damageUnit:
      - offset: 10
        code: |
          var int temp;
          battle_game.getAnimFrame temp;
          debug_log 10 temp;
          debug_log 20 to_health;
          unit.setTag Tag.LAST_HIT_FRAME temp;
          return;

I launch the game, start New Battle, shoot someone, exit, look through openxcom.log. There is nothing there O_o

Offline Yankes

  • Moderator
  • Commander
  • ***
  • Posts: 2141
    • View Profile
Re: Scripting basics
« Reply #12 on: August 18, 2019, 12:58:42 pm »
Debug message is only visible if you run game with debug mode enabled (avaialbe in user config).

For inventory right now only way to detect if unit have item is to check `getOwner`.

As my scripts was created from scratch, and it only grain new functionality when I add new script and it need something new.

As I said before, some obvious functionality could be missing in my scripts, and for next my project I will try fix it, but for now what exactly you want do? because It could be work around this.

Offline Kzer-Za

  • Colonel
  • ****
  • Posts: 140
    • View Profile
Re: Scripting basics
« Reply #13 on: August 18, 2019, 04:28:10 pm »
Quote
Debug message is only visible if you run game with debug mode enabled (avaialbe in user config).

Ah, now I see them! :)

Quote
what exactly you want do? because It could be work around this.

Well, I'm trying to prevent the above-said grenades from "exploding" after the death of the owner.

If I kill the owner Celatid during my turn, everything is ok, because their timers are set to odd numbers (5, 11, 17, etc), so they would go off at the end of the alien turn. Let's say, the owner Celatid is killed and it's my turn (say, the 4th). When the turn changes to the alien turn, it becomes the 5th, newTurnItem event is triggered, the timer gets set to -1, so the grenade is unprimed, and it does not "explode" at the end of the turn 5. So far so good.

But what if the Celatid gets killed by reaction fire or friendly fire from some Muton during the turn 5, 11, or 17? The event newTurnItem has been triggered in the beginning of this turn, and since the owner Celatid was alive, it did not do anything. Now the Celatid is killed, but the grenade is still primed, and at the end of the turn it works, and behold: the Celatid corpse spawns a live clone!

That is why I want to check the moment of death (by comparing to_health and unit.getHealth during damageUnit event), at that moment get the items owned by the dying Celatid, and unprime its grenades. Or better yet, destroy them or remove from the game, if it's possible.
« Last Edit: August 18, 2019, 04:29:45 pm by Kzer-Za »

Offline Yankes

  • Moderator
  • Commander
  • ***
  • Posts: 2141
    • View Profile
Re: Scripting basics
« Reply #14 on: August 18, 2019, 06:50:14 pm »
After looking to code I did not find any script that is run for item between moment of unit death and grenade explosion.
Next turn events are called after this.

To fully support this I would need ad new script to handle this.