OpenXcom Forum

Modding => OXCE Support => OpenXcom Extended => OXCE Support Y-scripts => Topic started by: Kzer-Za on August 15, 2019, 02:04:14 pm

Title: [Chat] Scripting basics
Post by: Kzer-Za 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?
Title: Re: Scripting basics
Post by: Kzer-Za on August 16, 2019, 01:52:14 pm
Okay, I'm flying blind here, but I'm gonna try to make Celatids clone themselves (https://openxcom.org/forum/index.php/topic,7278.0.html).

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.
Title: Re: Scripting basics
Post by: Yankes 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;
?
Title: Re: Scripting basics
Post by: Yankes 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.
Title: Re: Scripting basics
Post by: Kzer-Za 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?
Title: Re: Scripting basics
Post by: Kzer-Za 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?
Title: Re: Scripting basics
Post by: Yankes 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.
Title: Re: Scripting basics
Post by: Kzer-Za 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?
Title: Re: Scripting basics
Post by: Yankes 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.
Title: Re: Scripting basics
Post by: Kzer-Za 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?
Title: Re: Scripting basics
Post by: Yankes 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.
Title: Re: Scripting basics
Post by: Kzer-Za 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
Title: Re: Scripting basics
Post by: Yankes 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.
Title: Re: Scripting basics
Post by: Kzer-Za 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.
Title: Re: Scripting basics
Post by: Yankes 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.
Title: Re: Scripting basics
Post by: Kzer-Za on August 18, 2019, 08:52:01 pm
A pity. Well, I suppose, getting killed during the alien turn is not gonna be a common occurrence.

Can I ask another question? Is there any place where I can look up a list of possibilities of your languange? Inbuilt variables (like to_health), commands (or what is it called), like unit.setHealth, and so on, so I could not bug you on every little thing.
Title: Re: Scripting basics
Post by: Yankes on August 19, 2019, 01:44:23 am
Verbose logging dump all script meta data in log.
Title: Re: Scripting basics
Post by: Kzer-Za on August 19, 2019, 02:36:47 pm
Thanks!

I've been looking through the possibilities and found two operations for BattleUnit: getFatalwounds and getFatalwoundsTotal. What is the difference between them? When I try to assign the first of them to a variable I get an error, and with the second, I don't:

Code: [Select]
    damageUnit:
      - offset: 10
        code: |
          var int wounds;
          var int woundsTotal;
          unit.getFatalwounds wounds;
          unit.getFatalwoundsTotal woundsTotal;

This is the error:

Code: [Select]
[19-08-2019_14-18-41] [ERROR] Can't match overload for operator 'BattleUnit.getFatalwounds' for:
[19-08-2019_14-18-41] [ERROR]   [ptre BattleUnit] [var int]
[19-08-2019_14-18-41] [ERROR] Excepted:
[19-08-2019_14-18-41] [ERROR]   [ptr BattleUnit] [var int] [int]
[19-08-2019_14-18-41] [ERROR] Error in parsing script 'damageUnit' for 'Global Event Script': invalid operation in line: 'unit.getFatalwounds wounds;'

As far as I understand, BattleUnit.getFatalwounds is expecting two arguments? What is the second one?

Also, when is selectMoveSoundUnit hook is triggered? When a unit is moved?

Thanks for your patience!
Title: Re: Scripting basics
Post by: Yankes on August 19, 2019, 08:25:29 pm
Do you remember medkit screen? Each part have its own wounds. This function do this exactly, you can access each part wounds (you have predefined const variables that have body parts numbers).
`getFatalwoundsTotal` is simple sum of all parts, useful if you want only total value.


`selectMoveSoundUnit`: For each unit step this hook is triggered, it do more times than needed, IIRC original game play sound only on 0step, 1step, 3step and 7step.
Depending on move type/unit it could change.
And remember if you try play too many sound at once most of them will clip.

This function have lot of parameters for exposing add information used when deterring original unit sound. You can ignore most of them and play some arbitrary sound. or if you are fancy, you could create new step sound that can be affected by tile.
Title: Re: Scripting basics
Post by: Kzer-Za on August 19, 2019, 09:45:36 pm
Regarding `selectMoveSoundUnit`, actually I thought of using it to stop powersuits from spending energy when flying. Trigger it with this hook, then check if "BattleUnit.isFlying", then manipulate energy with getEnergy and setEnergy. You know, since the soldiers are not actually relying on their own body for flying, the work is done by the suit :)
Title: Re: Scripting basics
Post by: Yankes on August 19, 2019, 11:59:55 pm
Usually scripts that calculate something can't change any thing else.
Overall I limit what scripts can do because I can reason what can be changed and can easy refactor code without worrying about breaking scrips.
And vice versa. I try make that scripts can't break game, usually game can handle all that scripts throw can at it.

Bottom line is `selectMoveSoundUnit` only can calculate sound and nothing else (`ptr` vs `ptre`).
Title: Re: Scripting basics
Post by: Kzer-Za on August 20, 2019, 05:17:56 pm
Okay, thanks for the explanation.
Title: Re: Scripting basics
Post by: Kzer-Za on August 20, 2019, 06:04:08 pm
Oh, I forgot to ask one more thing: I don't see a way to randomize anything inside a script. Am I missing something, or is this possibility absent for now?
Title: Re: Scripting basics
Post by: Yankes on August 20, 2019, 08:51:03 pm
As game use static random state you can use it only in script that can change game state, its available in `BattleGame` object. Name of function is `randomChance` and `randomRange`.
Title: Re: Scripting basics
Post by: Kzer-Za on August 20, 2019, 10:09:42 pm
Could you please give an example of how it's used? I tried this:

Code: [Select]
    scripts:
      createItem: |
        var int chance1;
        var int chance2;
        BattleGame.randomChance chance1;
        debug_log 1 chance1;
        BattleGame.randomRange chance2 0 3;
        debug_log 2 chance2;
        return;

I thought that BattleGame.randomChance assigns a random number (0-99 or something like that) to a variable, and BattleGame.randomRange assigns to a variable a number in range (between 0 and 3 in the case above). But these return errors.
Title: Re: Scripting basics
Post by: Yankes on August 20, 2019, 10:17:48 pm
I probably do not say this accurate, `BattleGame` is type of object. Name of variable (in case of `createItem`) is `battle_game`.

Your script should look like:

Code: [Select]
battle_game.randomChance chance1;

btw one tidbit, this line is equal to:

Code: [Select]
BattleGame.randomChance battle_game chance1;

this is short hand that allow you to skip repeating name of type because we already know it from `battle_game`.
Error message should show you what exactly `BattleGame.randomChance` need as agreements.
Title: Re: Scripting basics
Post by: Kzer-Za on August 20, 2019, 11:20:13 pm
Aha, thanks!
Title: Re: Scripting basics
Post by: Kzer-Za on August 22, 2019, 05:18:21 pm
Could you please describe the hook psiDefenceBonusStats? It seems to me that if I try to change the "bonus" it returns, it takes effect only after the success of the attack is calculated. For example, for this experiment I tried this:

Code: [Select]
    psiDefenceBonusStats:
      - offset: 10
        code: |
          set bonus 1;
          debug_log 1 bonus;
          return bonus;

I psi-attack a unit two times by a soldier with a rather low psi-attack rating. In the log for both attacks I see "1 1" written. But the first attack always fails, even if it is Panic. The second always succeeds, even if it is Mind Control. So it seems that the bonus affects only the subsequent attacks, but not the current one. Am I right?

Also if I just output the bonus like this:

Code: [Select]
    psiDefenceBonusStats:
      - offset: 10
        code: |
          debug_log 1 bonus;
          return bonus;

it is equal to the "pure" Defense Strength of the attacked unit, without the Difficulty modifier (I mean the formula: Defense Strength (DS) = Psi Strength + (Psi Skill / 5 ) + Difficulty (from here https://www.ufopaedia.org/index.php/Psionics)). Does it mean the Difficulty is hard-coded and can't be affected?

Also, am I right that I cannot from this script access the attack strength of the incoming attack and change it? Or take into account some additional characteristics of the attacker?
Title: Re: Scripting basics
Post by: Yankes on August 22, 2019, 10:54:54 pm
First of all script with ending `BonusStats` are stats bonuses, This is used to calculate some stats of item or armor.
Armor have property named `psiDefence` that is used in psi attack calculations:
https://github.com/MeridianOXC/OpenXcom/blob/oxce-plus/src/Battlescape/TileEngine.cpp#L3823

You compare psi attacker value (e.g. item `accuracyMindControl` + `accuracyMultiplier`)  to pis defense strength (armor `30` + `psiDefence`) and some other factors.
If this is greater than 0 then attack success.

One drawback of this script types that they only have access to "owning" unit and nothing else. Because this is expansion of old implementation of bonus stats mechanic. And it do not have access to anything other than unit, and this script group inherited this limitation.

Proper psi attack scripts (calculation + custom effects) are planed in some future.
Title: Re: Scripting basics
Post by: Kzer-Za on August 23, 2019, 11:15:51 pm
I will be waiting eagerly :) In the meantime, I suppose, there is no way to add some damage to psi attacks?
Title: Re: Scripting basics
Post by: Kzer-Za on August 24, 2019, 11:07:27 pm
How can I trigger accuracyMultiplierBonusStats only for soldiers? I tried using tags, but neither

Code: [Select]
soldiers:
  - type: XCOM
    armor: STR_NONE_UC
    tags:
      SOLDIER: 1

nor

Code: [Select]
units:
  - type: XCOM
    armor: STR_NONE_UC
    tags:
      SOLDIER: 1

works.

Also I would like it to apply to all weapons except psionic ones, and I can't get item rulesets from within accuracyMultiplierBonusStats. Could you advise something about it?
Title: Re: Scripting basics
Post by: Meridian on August 25, 2019, 12:01:01 am
what do you want to do?

accuracy and damage can be modded to oblivion also without scripts, have you checked the existing ruleset attributes?
Title: Re: Scripting basics
Post by: Yankes on August 25, 2019, 03:04:14 am
There is separate attack on psi-amp that do weapon damage. Aliens can use it too.
Title: Re: Scripting basics
Post by: Kzer-Za on August 25, 2019, 07:35:27 am
what do you want to do?

accuracy and damage can be modded to oblivion also without scripts, have you checked the existing ruleset attributes?

Yes, they can, but I want to give soldiers a penalty to accuracy when they are flying, and that does not seem to be doable without scripts. The script for reducing the accuracy is already working, here it is:

Code: [Select]
    accuracyMultiplierBonusStats:
      - offset: 10
        code: |
          var int unitFloating;
          var int unitSoldier;
          var ptr RuleItem itemRuleset;

          unit.isFloating unitFloating;
          if eq unitFloating 1;
            muldiv bonus 8 10;
          end;
         
          debug_log 1 bonus;
          debug_log 2 unitFloating;
         
          return bonus;

Now I only need to insert a check for whether a unit is a soldier or not, since I don't want to affect other flying units. (Since soldiers in flying suits are affected by not having a foothold, while for massive hovertanks and cyberdiscs it is not such an issue, and flying aliens should be adapted to it, since flying is their natural means of locomotion).
Title: Re: Scripting basics
Post by: Kzer-Za on August 25, 2019, 07:41:37 am
There is separate attack on psi-amp that do weapon damage. Aliens can use it too.

Really? Can I combine it with the regular attacks? I wanted Mind Control to inflict minor damage (say, 2-3 HP). It is insignificant from one attack, but with repeated attacks it would take its toll.
Title: Re: Scripting basics
Post by: Yankes on August 25, 2019, 01:13:12 pm
This work different, You can have panic, MC and use action on psi-amp that will use weapon hit behavior. And they are separate options.

For penalty for only soldiers, you check if unit is not 2x2 (`getSize` in armor rule) and have soldier object `getGeoscapeSoldier` (if soldier have big armor then we assume it have stabilizers).
Title: Re: Scripting basics
Post by: Kzer-Za on August 25, 2019, 05:41:06 pm
Thanks, but how do I declare the variable for holding "getGeoscapeSoldier"? I tried this:

Code: [Select]
          var ptr BattleUnit isSoldier;
         
          unit.getGeoscapeSoldier isSoldier;
          if neq isSoldier null;
            debug_log 0 12345;
          end;

But I get an error "Conflicting overloads for operator 'clear'". If I try "var ptr GeoscapeSoldier isSoldier;", I get "invalid type" error.
Title: Re: Scripting basics
Post by: Shiroi Bara on August 25, 2019, 06:13:06 pm
A few questions:
1. How to nerf psi attack skills of enemy? Should I reduce parameter psiSkill in units.rul file?
2. What formulas used to calculate Psi (M.C.) attack in both UFO and TFTD based games?
3. Is it possible generate alien mission when only proper research finished and if yes how to do it?
Answers with examples will be great.
Title: Re: Scripting basics
Post by: Kzer-Za on August 25, 2019, 06:29:54 pm
I think it would be better to create a separate theme for these questions for readability sake, since they are not related to scripting (mission scripts are a different kind of scripts, not the one discussed here).

PS I answered your questions briefly in a private message, to keep this theme about scripts.
Title: Re: Scripting basics
Post by: Kzer-Za on August 25, 2019, 09:03:46 pm
I would also like to have tags on soldiers as BattleUnits for other purposes, is there a way for it? As I said, neither

Code: [Select]
soldiers:
  - type: XCOM
    tags:
      SOLDIER: 1

nor

Code: [Select]
units:
  - type: XCOM
    tags:
      SOLDIER: 1

is working. (By not working I mean that when I try to get the tag, the result is 0).
Title: Re: Scripting basics
Post by: ohartenstein23 on August 25, 2019, 10:49:20 pm
Does your mod have a soldier or unit type called "XCOM"? If you haven't specifically added that unit or soldier, the standard X-Com soldier is called "STR_SOLDIER" internally.
Title: Re: Scripting basics
Post by: Kzer-Za on August 25, 2019, 11:15:37 pm
(Banging my head on the wall) I have copied it from somewhere and didn't change the type. Thanks!
Title: Re: Scripting basics
Post by: Kzer-Za on August 26, 2019, 10:13:56 am
But after the correction of the mistake above, I still get zeroes. Probably another stupid mistake on my part, but I don't see it :( Here's the code:

Code: [Select]
extended:
  tags:
    BattleUnit:
      LAST_HIT_FRAME: int
      IS_SOLDIER: int
  scripts:
    accuracyMultiplierBonusStats:
      - offset: 10
        code: |
          var int unitFloating;
          var int unitSoldier;
          unit.getTag unitSoldier Tag.IS_SOLDIER;
          debug_log 96580879 unitSoldier;
          return bonus;
units:
  - type: STR_SOLDIER
    tags:
      IS_SOLDIER: 1

And after clicking on a weapon in the Battlescape I get this in the log:
Code: [Select]
[26-08-2019_10-00-05] [DEBUG] Script debug log at     0x2d: 96580879 0

I can't see what am I doing wrong now.
Title: Re: Scripting basics
Post by: Meridian on August 26, 2019, 10:40:38 am
vanilla STR_SOLDIER is under soldiers: not units:
Title: Re: Scripting basics
Post by: Kzer-Za on August 26, 2019, 10:57:21 am
I thought it should be in "units" because if I define it in "soldiers" I get an error. The rest of the code is the same as above, only "units" are replaced with "soldiers":

Code: [Select]
soldiers:
  - type: STR_SOLDIER
    tags:
      IS_SOLDIER: 1

Code: [Select]
[ERROR] Error in tags: 'IS_SOLDIER' unknown tag name not defined in current file
Title: Re: Scripting basics
Post by: Meridian on August 26, 2019, 12:05:44 pm
Code: [Select]
extended:
  tags:
    RuleSoldier:
      MY_TAG: int
  scripts:
    accuracyMultiplierBonusStats:
      - offset: 10
        code: |
          var int myTagValue;
          var ptr RuleSoldier ruleSoldier;
          unit.getRuleSoldier ruleSoldier;
          ruleSoldier.getTag myTagValue Tag.MY_TAG;
          debug_log 2019 myTagValue;
          return bonus;
soldiers:
  - type: STR_SOLDIER
    tags:
      MY_TAG: 77

Don't forget to check ruleSoldier for NULL for doing anything serious.
Title: Re: Scripting basics
Post by: Kzer-Za on August 26, 2019, 12:26:23 pm
Aaah, the type of rule in "extended" that has this tag! Not BattleUnit but RuleSoldier. Thanks!
Title: Re: Scripting basics
Post by: Kzer-Za on August 26, 2019, 02:59:25 pm
I just found that damageUnit is not triggered when a soldier loses health due to bleeding. So if I want to get the amount of health that he/she loses due to bleeding, I guess, I have to use "newTurnUnit" and "unit.getFatalwoundsTotal", and no shorter way for it?
Title: Re: Scripting basics
Post by: Yankes on August 26, 2019, 10:07:56 pm
Thanks, but how do I declare the variable for holding "getGeoscapeSoldier"? I tried this:

Code: [Select]
          var ptr BattleUnit isSoldier;
         
          unit.getGeoscapeSoldier isSoldier;
          if neq isSoldier null;
            debug_log 0 12345;
          end;

But I get an error "Conflicting overloads for operator 'clear'". If I try "var ptr GeoscapeSoldier isSoldier;", I get "invalid type" error.
Ok, after looking to this example I saw that I do not registered correctly this type for all scripts. I will fix it in next version.

I just found that damageUnit is not triggered when a soldier loses health due to bleeding. So if I want to get the amount of health that he/she loses due to bleeding, I guess, I have to use "newTurnUnit" and "unit.getFatalwoundsTotal", and no shorter way for it?
Yes, this is similar to fire damage that is handled by other part of code in next turn event.
Title: Re: Scripting basics
Post by: Kzer-Za on August 27, 2019, 09:08:58 pm
Sorry to bother you again, but is there a way to get RuleArmor from BattleUnit? I'm interested in getting the unit's armor from "accuracyMultiplierBonusStats" hook, it is needed for this: https://openxcom.org/forum/index.php/topic,7326.msg116055.html#msg116055
Title: Re: Scripting basics
Post by: Yankes on August 27, 2019, 10:47:03 pm
`getRuleArmor` don't work?
Title: Re: Scripting basics
Post by: Kzer-Za on August 28, 2019, 10:51:35 am
Oh, it does. I probably tried using "getArmor" before. Thanks!
Title: Re: Scripting basics
Post by: Kzer-Za on August 30, 2019, 07:36:26 pm
Sorry, but I have several questions again :)

1. How do I get the thickness of armor? I tried:

Code: [Select]
        var ptr RuleArmor armorRuleset;
        var int newArmor;

        unit.getRuleArmor armorRuleset;
        armorRuleset.getArmor 0 newArmor;

As far as I undestand, I have to specify the side, with 0 being the frontal armor. I also tried switching 0 and "newArmor" places, since I'm not sure about the order of the arguments, but it didn't work either.

2. How can I "truly" kill a unit the moment it is spawned? I mean, I can use "unit.setHealth 0;" in "createUnit", but the unit is spawned in alive state, and dies only when it is hit (even for 0 damage) or after the end of turn. Is there a way to kill it right off? I need it because I have a "createUnit" script that subtracts hitpoints depending on different conditions and in some cases I want a unit to be spawned dead.

3. Can I make a "createUnit" script affect only the units that are spawned during the combat, but not the "default" units that are supposed to be "prespawned" before the combat is started? I tried checking for "turn" variable, but these units are considered to be spawned during the turn 1. And this turn also covers the first player's turn as well as the first aliens' turn. And I need these turns to be treated as "during the combat" already. Could you think of a condition that would exclude these "prespawned" units?
Title: Re: Scripting basics
Post by: Yankes on August 30, 2019, 10:47:50 pm
1.
Code: [Select]
    unit.getArmor value side;
    unit.getArmorMax value side;
btw, in next version I will add new consts: this is already added, I looked on wrong init function.
Code: [Select]
SIDE_FRONT
SIDE_LEFT
SIDE_RIGHT
SIDE_REAR
SIDE_UNDER

2. There is special code in game that handle dying units, problem is that it is not run after unit spawn and unit will be "undead" unit this function is called (like you shoot any thing or press next turn). Probably correct fix is call this function after unit is spawned. I can do it.

3. Good point, I can change turn value to 0 when unit/item is spawned before you start first turn.
Title: Re: Scripting basics
Post by: Kzer-Za on August 31, 2019, 02:24:11 pm
1. Thanks, now I got it!

2, 3. That's great to hear! When (approximately) is it expected to be introduced?
Title: Re: Scripting basics
Post by: Yankes on September 01, 2019, 12:01:49 pm
3 is already done, 2 will be soon (TM from Valve or Blizzard :>)
Title: Re: Scripting basics
Post by: Kzer-Za on September 01, 2019, 01:30:34 pm
Great! :)

I have run into one problem after updating today to OXCE version 5.6.2 (v2019-08-28). You obviously fixed something, but I, not knowing that it was not supposed to work, relied on this in one mod (it even already found some users), it's the one about cloning Celatids, I asked you about it a lot earlier in this theme :)

So, I used this code:

Code: [Select]
      newTurnItem: |
        var ptre BattleUnit itemOwner;
        var int itemOwnerTUmax;
        var int timer;
        item.getOwner itemOwner;
        # unprime gemmae not held by a Celatid
        if eq itemOwner null;
          item.setFuseTimer -1;
        end;
        # spend time on cloning
        item.getFuseTimer timer;
        if eq timer 0;
          itemOwner.getTimeUnitsMax itemOwnerTUmax;
          div itemOwnerTUmax 2;
          itemOwner.setTimeUnits itemOwnerTUmax;
        end;
        return;

It worked before, but now I get these errors:

Code: [Select]
[01-09-2019_13-14-02] [ERROR] Can't match overload for operator 'BattleItem.getOwner' for:
[01-09-2019_13-14-02] [ERROR]   [ptre BattleItem] [var ptre BattleUnit]
[01-09-2019_13-14-02] [ERROR] Excepted:
[01-09-2019_13-14-02] [ERROR]   [ptr BattleItem] [var ptr BattleUnit]
[01-09-2019_13-14-02] [ERROR]   [ptre BattleItem] [var ptr BattleUnit]
[01-09-2019_13-14-02] [ERROR] Error in parsing script 'newTurnItem' for 'STR_CELATID_GEMMA': invalid operation in line: 'item.getOwner itemOwner;'

As far as I understand, now it's impossible to addres to a BattleUnit as a ptre from BattleItem. And I kinda need it to change the amount of TUs of the Celatid on the turn, on which it clones itself. The problem is I don't want them to spawn clones on exact turns 5, 11, ..., so during the "createItem", the fuse timer is randomized a little. I can't think of a way for a Celatid to get the fuse timers from the grenades it is carrying, that is why I went the other way and changed the TUs of the item owner from the item (grenade), since from the grenade I can get its timer.

So I was wandering how I can do it now?
Title: Re: Scripting basics
Post by: Yankes on September 01, 2019, 02:16:58 pm
What version worked and what do not? Recently I made some refactor using C++17 features and there is possibility I break something.

Error is clear:
Code: [Select]
[01-09-2019_13-14-02] [ERROR] Can't match overload for operator 'BattleItem.getOwner' for:
[01-09-2019_13-14-02] [ERROR]   [ptre BattleItem] [var ptre BattleUnit]
[01-09-2019_13-14-02] [ERROR] Excepted:
[01-09-2019_13-14-02] [ERROR]   [ptr BattleItem] [var ptr BattleUnit]
[01-09-2019_13-14-02] [ERROR]   [ptre BattleItem] [var ptr BattleUnit]
He did not see version when we have editable item and want editable unit. And AFAIR there should be version like that available.
Title: Re: Scripting basics
Post by: Kzer-Za on September 01, 2019, 02:28:36 pm
The version that I used up to this morning was 5.6.1 (v2019-08-03), that's where things worked. Today I compiled the new version, which is 5.6.2 (v2019-08-28), that's where the error occurs.
Title: Re: Scripting basics
Post by: Yankes on September 01, 2019, 02:48:36 pm
I can confirm I made error that incorrectly detect "return type".
I will soon fix this bug.

[ps]

fix should be ready, you can compile fresh version and see if it work correcly
Title: Re: Scripting basics
Post by: Kzer-Za on September 01, 2019, 05:02:24 pm
Thanks!
Title: Re: Scripting basics
Post by: N7Kopper on September 27, 2019, 10:26:21 pm
After spending a fair bit of time reading through the script variables (a slightly out-of-date list, no references to mana, but I don't need it) I've realised that I still can't figure out how to specifically check for a unit being killed by an attack. Perhaps it's my inexperience with reading scripting languages or me rolling a suite of 1s on Spot, but it looks to be a fair bit harder to check for a unit being killed - or surviving, either works - than to check for taking no damage at all. (if eq to_health 0)

Yes, I'm having a crack at making Chryssalids and Tentaculats only zombify when they reduce the human target's HP to 0, not on any successful hit. (Hell, not even them specifically, a generic script that makes zombification work that way in general would be a nice example script) It'd be a nice learning experience for me to be able to make it.
Title: Re: Scripting basics
Post by: Meridian on September 27, 2019, 10:36:01 pm
but it looks to be a fair bit harder to check for a unit being killed - or surviving, either works - than to check for taking no damage at all. (if eq to_health 0)

is checking whether to_health >= currentHealth enough? or are you looking for something more elaborate?
Title: Re: Scripting basics
Post by: N7Kopper on September 28, 2019, 05:05:27 pm
is checking whether to_health >= currentHealth enough? or are you looking for something more elaborate?
Anything that checks for units not dying from that strike (and properly factors in who ought to be infected) is good enough, thanks. I admit, I'm not the best at this sort of thing, but next time I look at this I'll do that check.

It would probably help if I annotated the giant list of script parameters I have. :P

Anyways, if all goes well, I'll be coming back to this topic with a nicely debugged mod.