Okay, so I've made some good progress and can show off some effects. Plus, some people thought sharing this with a wider audience might be beneficial. The current code fork is
here and working, though still not done and WIP.
So with this new scripting capability, you can essentially draw anything onto the screen and even animate it to a degree. However, as a practical matter, "bliting" or drawing a sprite on top of another sprite is probably the most useful. Some example effects.
By replacing a weapon like the heavy cannon ammo clip sprite with a new "empty" sprite, you can then blit that full sprite onto it. But then
crop that sprite as ammunition is expended to show that ammunition has been expended. For example:
As shown in the example, only one base clip needs to be prepared, and you can apply the same effect to all of them. With a well-designed script, you can even use the same principle across different weapon types, for example...
These sorts of "diegetic" elements can render the same across all locations the item is rendered (except the ufopedia), even if you pick the item up with your cursor. However, you can also control when they take effect if you like.
In any case, the script to produce this effect is fairly straightforward.
extended:
tags: # Remember to add these tags to the same file as items ruleset
RuleItem:
NEW_BIGOB: int # new clip. This will be the empty clip.
CURRENT_MOD_OFFSET: RuleList
# CROP_MULT_L or CROP_MULT_R should be 0 if that side of the crop is static.
CROP_MULT_L: int # amount to crop left per spent round
CROP_MULT_R: int # amount to crop right per spent round (negative if feeding from left)
CROP_OFFS_L: int # amount to offset the crop from the left.
CROP_OFFS_R: int # amount to offset the crop from the right.
scripts:
selectItemSprite:
# replaces an existing sprite with a new one, everwhere but the ufopedia.
- new: SpriteReplacement
offset: 2
code: |
if eq blit_part blit_item_big;
var int newSprite;
var ptr RuleItem ruleItem;
item.getRuleItem ruleItem;
ruleItem.getTag newSprite Tag.NEW_BIGOB;
if neq newSprite 0;
var int spriteIndex;
var int modOffset;
set sprite_index newSprite;
ruleItem.getTag modOffset Tag.CURRENT_MOD_OFFSET;
rules.getSpriteOffsetBigobs sprite_index modOffset;
return sprite_index;
end;
end;
return sprite_index;
inventorySpriteOverlay:
# the script works by bliting the a cropped portion of a full clip onto an image of an empty clip
# (replaced by script in SpriteReplacement, so the correct image shows in ufopedia)
# the values need to be set so that the cropOffL and cropOffR represent the min and max x
# of the full clip to display, in the weapons current state.
- new: ShowUsedAmmo
offset: 1
code: |
var ptr Sprite fullclip;
var ptr RuleItem ruleItem;
var int cropMulL; # the amount to crop per round on the left side
var int cropMulR; # the amount to crop per round on the right side
item.getRuleItem ruleItem;
ruleItem.getTag cropMulL Tag.CROP_MULT_L;
ruleItem.getTag cropMulR Tag.CROP_MULT_R;
if or neq cropMulL 0 neq cropMulR 0;
var int ammoLeft; # the number of rounds remaining.
var int ammoMax; # the max number of rounds.
var int cropOffL; # the amount to offset the left crop.
var int cropOffR; # the amount to offset the right crop.
var int fullClipId; # id of the original cip
item.getAmmoQuantity ammoLeft;
ruleItem.getClipSize ammoMax;
ruleItem.getTag cropOffL Tag.CROP_OFFS_L;
ruleItem.getTag cropOffR Tag.CROP_OFFS_R;
# calculate the crops The formula is. cropOff = (ammoMax - ammoLeft) * cropMul + cropOff
sub ammoMax ammoLeft; # ammoMax = ammoMax - ammoLeft
mul cropMulL ammoMax; # cropMulL = cropMulL * ammoLeft
mul cropMulR ammoMax; # cropMulR = cropMulR * ammoLeft
add cropOffR cropMulR; # cropOffR = cropOffR + cropMulL
add cropOffL cropMulL; # cropOffL = cropOffL + cropMulL
# get the original clip sprite
ruleItem.getBigSpriteIndex fullClipId;
rules.getSpriteFromSet fullclip "BIGOBS.PCK" fullClipId;
# blit the original sprite onto the empty sprite
# with a crop from (cropOffL, 0) to (cropOffR, 47)
overlay.blitCrop fullclip cropOffL 0 cropOffR 47;
end;
return;
items:
# heavycannon ammo appears to feed from the right, 4px per round.
# Starting 6px from the left. The right side is fixed at 31px.
- type: STR_HC_AP_AMMO
tags:
NEW_BIGOB: 201 # empty AC ammo.
CURRENT_MOD_OFFSET: UsedAmmo
CROP_MULT_L: 4
CROP_OFFS_L: 6
CROP_MULT_R: 0
CROP_OFFS_R: 31
- type: STR_HC_HE_AMMO
tags:
NEW_BIGOB: 201 # empty AC ammo.
CURRENT_MOD_OFFSET: UsedAmmo
CROP_MULT_L: 4
CROP_OFFS_L: 6
CROP_MULT_R: 0
CROP_OFFS_R: 31
- type: STR_HC_I_AMMO
tags:
NEW_BIGOB: 201 # empty AC ammo.
CURRENT_MOD_OFFSET: UsedAmmo
CROP_MULT_L: 4
CROP_OFFS_L: 6
CROP_MULT_R: 0
CROP_OFFS_R: 31
# autocannon ammo appears to feed from the left, -2px per round.
# Starting 31px from the left. The left side is fixed at 0px.
- type: STR_AC_AP_AMMO
tags:
NEW_BIGOB: 202 # empty AC ammo.
CURRENT_MOD_OFFSET: UsedAmmo
CROP_MULT_L: 0
CROP_OFFS_L: 0
CROP_MULT_R: -2
CROP_OFFS_R: 31
- type: STR_AC_HE_AMMO
tags:
NEW_BIGOB: 202 # empty AC ammo.
CURRENT_MOD_OFFSET: UsedAmmo
CROP_MULT_L: 0
CROP_OFFS_L: 0
CROP_MULT_R: -2
CROP_OFFS_R: 31
- type: STR_AC_I_AMMO
tags:
NEW_BIGOB: 202 # empty AC ammo.
CURRENT_MOD_OFFSET: UsedAmmo
CROP_MULT_L: 0
CROP_OFFS_L: 0
CROP_MULT_R: -2
CROP_OFFS_R: 31
This effect may not seem very useful (though I like it!). But it demonstrates the ability to write one sort of effect/asset and then reuse it multiple times easily. Doing this with sprite replacement (only) would be a lot more work.
Next up, we have the ability to write numbers onto the interface. Same as numbers are currently written for ammo, medkit charges, etc. These sorts of effects can be recreated exactly (and in previous drafts, they were). So you can use them to define your own sort of interface if wanted. But you can also use them in other creative ways like...
So I hope what's happening here is obvious. But this also demonstrates some limited ways you can animate effects. Since the scripts all take the anim_frame member, if you can build a transformation around that value, you can use it to power an animation. Like the blinking light effect shown here. In addition, you might notice that the normal grenade-primed indicator has been turned off, which is another thing you can do with the scripting.
items:
- type: STR_HIGH_EXPLOSIVE
bigSprite: 203
scripts:
inventorySpriteOverlay: |
var int timer;
# unset the grenadeIndicator, we don't want it.
overlay.unsetOptions OVERLAY_DRAW_GRENADE_INDICATOR;
item.getFuseTimer timer;
if ge timer 0;
var int offsetX 14; # centered in frame for 1 digit
var int flash anim_frame;
var int period timer;
# if greater than 10, offset by -2 to allow space
if ge timer 10;
sub offsetX 2;
end;
# Flash period based on timer. Period starts at 6
# and increases by 3 every other timer increment.
# period = (period / 2 + 2) * 3
div period 2;
add period 2;
mul period 3;
# generate saw wave but only display timer if value greater than 2.
# this gives a fixed "off" period of 3 ticks (0, 1, 2) with the on period
# increasing with timer length.
wavegen_saw flash period period 40;
if gt flash 2;
overlay.drawNumber timer 7 5 offsetX 3 55;
end;
end;
return;
One last one, showing a combination of the two effects.
In this example, the normal proximity grenade sprite has been replaced with an "unlit" sprite (replaced via rule file, not script, so the change will appear in the ufopedia). However, when the grenade is primed, we blit another image on top to represent the blinking light. Additionally, that light is shaded using a wave function to give it the "flashing" effect.
The script:
items:
- type: STR_PROXIMITY_GRENADE
bigSprite: 204 # unlit proximity grenade
scripts:
inventorySpriteOverlay: |
var int timer;
# unset the grenadeIndicator, we don't want it.
overlay.unsetOptions OVERLAY_DRAW_GRENADE_INDICATOR;
item.getFuseTimer timer;
if ge timer 0;
var ptr Sprite light;
var int shade anim_frame;
rules.getNamedSprite light "Proximity_Grenade_Light";
wavegen_tri shade 8 8 4;
add shade -3;
overlay.blitShade light 0 0 shade;
end;
return;
So that's all I got for right now. This is still a WIP, and I still have more to do, but I think these effects are really only scratching the surface of what is possible.
(To be clear, these are just demonstrations of what is possible, not a proposal to change the default behavior that remains unchanged, though I still have the tweaks I suggested earlier).