Author Topic: Secondary animations for triggered objects: Experimental version available  (Read 13768 times)

0 Members and 2 Guests are viewing this topic.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
https://www.youtube.com/watch?v=X4fwdCxnmOc

EDIT: Better video: https://www.youtube.com/watch?v=JVNiNPHUSwM

EDIT: Most up-to-date video: https://youtu.be/wutmgNuo-90

A long overdue feature. I just finished implementing it.

This allows objects like traps, teleporters, locked exits to have a constantly-animating component alongside the triggered animation. The constant component can either stop animating while the object is triggered, or continue regardless - this is a per-object setting. The animation always stops, in the case of a trap, if the trap is disarmed.

Uses for this include:
- Making traps stand out more
- Identifying disarmed traps without relying on clear physics mode
- Combining locked exits and exit decoration flames / lights into a single object
- Probably various artistic uses

The implementation is functionally 100% backwards-compatible. In terms of visuals, it may have oddities on older NL versions but will still be recognizable (unless the object's dual-animation design is really complex, perhaps). If an object with a secondary animation - even one that appears outside the object's original frame borders - is used in an older NL version that doesn't support secondary animations, the secondary animation simply isn't displayed. The primary animation still works fine, and the object will not be repositioned.



The attached experimental version will allow you to see this feature for yourself, as well as get an idea of how it's implemented for your own styles' usage. This experimental also contains several of the other recent fixes / changes, including the Shimmier skill.

To use this experimental build, first, set up a normal copy of (stable) NeoLemmix. Then, extract the contents of this ZIP over the top of it, overwriting any files if prompted. This includes some levels (under the "Single Levels" pack) which demonstrate these new animations.

It includes such animations for traps and locked exits in all official styles (including Sega), as well as all of my styles.

For those preparing their own styles - please see this post for information. Secondary animations are now supported with all visible-in-game object types, but some of the trigger conditions are only supported on certain animation types. Also note that masks are now replaced with secondary animations (which yes, can have the mask-like recoloring property); see default:owa_#### and default:pickup for examples.

Secondaries do not have to be the same size or have the same number of frames as the primary animation, and the primary can have a horizontal strip while the secondary has a vertical one (or vice versa).

As usual, no guarantees that any features in this experimental will remain as-is. I don't see the need for any further changes, though it's not exclusively up to me to make the final call on this. If you like the current state of this feature, make sure to express this, so that the development team as a whole know it's desired.

There is an experimental editor included as well. Please note - it's completely normal for this editor to take a long time to load the first time it runs, if NeoLemmix is also present - it's getting NeoLemmix to pre-render the combined graphic of any objects that have secondary animations.

If you add / remove / modify an object with secondary animations (or with recoloring on the primary animation), the editor does not automatically detect this and request a re-render of the graphic. The easiest way to get a re-render is to exit the editor, delete the "render" folder inside the "editor" folder, then run the editor - this will cause it to request re-rendering of all pieces it needs it for. Alternatively, for advanced users, you can use the command line arguments: "NeoLemmix.exe render -editor <style name> <style name> ...". These will make NeoLemmix re-render all objects in the specified style(s).

Remember, as is often the case: With great power, comes great responsibility. Use this feature in ways that produce neat artistic effects. Use this feature in ways that help clarify exactly what a trap or other object is or isn't doing. Don't use this feature in ways that are intended to be deceptive.
« Last Edit: May 16, 2019, 04:54:40 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Crane

  • Posts: 1078
    • View Profile
Re: Secondary animations for triggered objects
« Reply #1 on: May 02, 2019, 10:41:17 PM »
The trouble with the example video shown is that it looks like the flashing light is just a second object behind the bear trap.  If it, say, turns off when the trap is disarmed, then it's a little more convincing.

One thing I wondered if was possible is to have a fire trap that only animates if a lemming is being fried by it.  For example, an innocuous nozzle (no animation, or extremely basic) that suddenly emits a menacing blue and green gas flame (think a gas cooker) when a lemming walks on it, without switching off until there are no more lemmings.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Re: Secondary animations for triggered objects
« Reply #2 on: May 02, 2019, 10:46:55 PM »
Quote
The trouble with the example video shown is that it looks like the flashing light is just a second object behind the bear trap.  If it, say, turns off when the trap is disarmed, then it's a little more convincing.

It does. See the second video I've added. :)

Also, look closely at the animation when the trap is in use vs when it's idle - it doesn't blink green when the trap is busy. Of course, for some objects / animations it makes more sense if the animation does continue - so see how the boulder trap works in the new video. In both cases, the animation stops if the trap is disarmed.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Flopsy

  • Global Moderator
  • Posts: 953
  • Lix Nerd
    • View Profile
Re: Secondary animations for triggered objects
« Reply #3 on: May 02, 2019, 11:29:03 PM »
Looks really good this does namida :)

I'm excited to make use of this feature in my Sonic tilesets, is there anything I can do to prepare my tilesets for this feature. Like how would I make the image strip now with respect to these changes?

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Re: Secondary animations for triggered objects
« Reply #4 on: May 03, 2019, 12:01:01 AM »
Looks really good this does namida :)

I'm excited to make use of this feature in my Sonic tilesets, is there anything I can do to prepare my tilesets for this feature. Like how would I make the image strip now with respect to these changes?

Create the graphic strips as if you were creating two objects that always get placed together. They don't have to be the same size or have the same number of frames, but the relative position needs to always be the same - eg, the second one could be placed at an offset of 10 to the right and 5 down from the first one, as long as it's always placed at that offset (or the flipped / inverted / rotated equivalents, where applicable). The second one can either appear in front of or behind the first one; the way this feature works allows for either, at the object creator's choice.

« Last Edit: May 07, 2019, 08:40:50 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
I've added the experimental version, so you can see how it works in practice, prepare your own styles, report bugs, etc.

Suggestions are still welcome, but keep in mind this isn't intended to do really complicated fancy stuff - you'll still need multiple objects for that.
« Last Edit: May 03, 2019, 05:23:20 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline IchoTolot

  • Global Moderator
  • Posts: 3605
    • View Profile
This looks awesome! :thumbsup:

For the L2/L3 tilesets I would request external help though as I don't have time, motivation and nerves to completely draw these from scratch.

I may have some ideas like the cavelem eyes gloing a bit already, but getting ideas and the execution would take a ton of time.

Offline Simon

  • Administrator
  • Posts: 3851
    • View Profile
    • Lix
This is very good to distinguish traps from terrain.

The backwards compatibility to single-animation is a respectable and correct decision: It must be easy to design new tiles, a minimal number of files must suffice, to encourage creativity.



Primary/secondary are bad names, unspecific, is primary the idle animation because levels always start with traps idle, or is primary the busy animation because, as a relic of implementation history, Lemmings 1 had only the busy animation?

Consider idle and busy/activated/... as names.



Be careful to not paint yourself into a corner by restricting the number of possible animations to 2. Triggered traps already have 3 states: idle, busy, and disarmed. You've already seen in Lemmings 1 that it's bad to lump two states into the same animation.

It's fine if the disarmed animation is a single frame, at least it's far less of a problem than with idle traps.

You can even consider (a separate animation/state) (the process of getting disarmed), and after that a loop of the disarmed animation. Doesn't matter if this further separation is practical -- the gist is to not repeat a mistake of history (and maybe in Lix) and restrict number of animations. Otherwise, you'll end with nasty hacks to support future extra states.

-- Simon
« Last Edit: May 04, 2019, 01:40:48 PM by Simon »

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Regarding the above: Yeah, I thought about that regarding multiple animations - I realised it would be potentially useful for limited-lemming-count entrances and exits, which especially the latter is a highly-requested feature - so I'm now working on code to allow for any number of additional animations. Even if these never get used, at least they're there.
« Last Edit: May 04, 2019, 10:28:22 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
With regards to how the animations are actually stored, which layout is preferred?

For this, we'll use an example object that has:

a) A metainfo file, of course (the .nxmo file).
b) A main animation, which we'll call "busy".
c) A secondary animation, which we'll call "idle".
d) A mask (like the default pickup skills do), which we'll call "test_mask".

We'll call this object "namida_example:example_object". (Not sure if this notation is often used publicly; it means graphic set "namida_example", piece "example_object". Internally in NeoLemmix's workings, this notation is often used to specify the combination of graphic set and piece. Whether it refers to a terrain, an object or a background is inferred from context.)

Current setup in stable NL
a) The metainfo file is stored as "styles/namida_example/objects/example_object.nxmo"
b) The main animation is stored as "styles/namida_example/objects/example_object.png"
c) Secondary animations are not currently supported
d) The mask is stored as "styles/namida_example/objects/example_object_mask_test_mask.png"

Option 1 - Subfolder for entire object
a) The metainfo file is stored as either "styles/namida_example/objects/example_object.nxmo" or "styles/namida_example/objects/example_object/<some fixed filename>.nxmo"
b) The main animation is stored as "styles/namida_example/objects/example_object/busy.png"
c) The secondary animation is stored as "styles/namida_example/objects/example_object/idle.png"
d) The mask is stored as "styles/namida_example/objects/example_object/masks/test_mask.png" (Or perhaps the filename is prefixed with "mask", rather than in a "masks" subfolder)

Older pieces would need specific backwards-compatibility code in NeoLemmix to handle them - but this wouldn't be hard to do.

Option 2 - Prefixed filenames
a) The metainfo file is stored as "styles/namida_example/objects/example_object.nxmo"
b) The main animation is stored as "styles/namida_example/objects/example_object.png" (or "styles/namida_example/objects/example_object_busy.png")
c) The secondary animation is stored as "styles/namida_example/objects/example_object_idle.png"
d) The mask is stored as "styles/namida_example/objects/example_object_mask_test_mask.png"

If the alternate name under B isn't used, this is backwards / forwards compatible for the player. The editor has minor issues with the extra images, as it appears to find objects by looking for PNG files, rather than by looking for NXMO files, so new pieces won't work properly in old editor versions.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline IchoTolot

  • Global Moderator
  • Posts: 3605
    • View Profile
Option 2 - Prefixed filenames would be my choice.

I hate having too many folders. I rather have more files - even more if the pairs of 2-4 have fitting names. :P

I think this is much easier for tileset maintenance.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
I hate having too many folders. I rather have more files - even more if the pairs of 2-4 have fitting names. :P

Yes, they would. All files relating to an object would have filenames starting with the object's name. In the example above, all objects filenames would start with "example_object".
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Simon

  • Administrator
  • Posts: 3851
    • View Profile
    • Lix
Both options 1 or 2 look reasonable, it depends on personal liking. Yeah, the heavy lifters of our maintenance camp should pick. 8-)

-- Simon

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
I've come up with a more final - and far more powerful - implementation. It uses option 2 in regards to file naming, and allows for... not quite "anything you can imagine", but it's quite flexible. Multiple animations are supported, which can have different (and customizable) conditions. For example, you could have a trap with the normal main animation, a secondary animation that stops (or even disappears - or loops back to frame zero, then stops or disappears) when the trap is in use, another secondary animation that only plays while the trap is in use but continues until reaching frame 0 when the trap finishes... it's probably far more powerful than it needs to be, but it works well.

Currently, it supports telling the animation, based on the supported conditions, to pause, stop (differentiated by that stop also returns to frame 0), return to frame 0 then pause, play, or display the same frame as the primary animation (can be useful eg. for pickup skills). It can also show or hide the animation again based on these conditions - if the animation is told to hide, it remains visible until paused, allowing this to be combined with the "return to frame 0 then pause" feature.

I've decided to keep whatever animation would already currently exist, being defined as the "primary" one. The logic here - the primary one is the one that actually relates directly to physics. To use a trap as an example - how many frames the idling animation has is irrelevant to physics, but how many frames the active animation has is not. Therefore, it should be the primary one. Of course, backwards compatibility is another good reason for this.

The supported conditions, currently, are:
- Primary animation is on frame zero. This is supported on object types where frame zero is significant, eg. traps (including single-use), teleporters.
- Primary animation is on frame one. This is supported on object types where frame one is significant, eg. single-use traps, locked exits.
- Busy. This is supported on traps, teleporters and receivers. A teleporter or receiver will register as busy whenever the pair, as a whole, is in use.
- Triggered. This is supported on all object types that can, but don't always, animate (so it includes eg. entrances, locked exits, etc). The object will register as triggered while the primary animation is animating. Teleporters and receivers are NOT linked for the purpose of this one.
- Disabled. This is supported on traps (including single use, but a used single-use trap does not register as disabled - only a disarmed one does), teleporters and receivers (where it would register if no paired teleporter / receiver exists).

This is quite powerful, and can theoretically get very complex, but the vast majority of real-world use cases would simply need to copy / paste less than 10 lines from a default object that has similar secondary animation behaviour.

This does mean, secondary animation metainfo files from the earlier experimental, won't work as-is (though they're generally very easy to convert). Image files, on the other hand, can most certainly be used as-is under the new system - I didn't have to make a single change to any images I made for the earlier system.

Usage guide for current system (click to show/hide)

As a side effect of these changes, objects with recoloring masks (which are now implemented as recolored secondary animations) are now slightly more efficient. Previously, these were reloaded any time a new level was loaded, unless the new level used the same theme as the previously loaded level. Now, the "keep existing copy" test is based on the specific color rather than the theme (so two different themes with the same color = no reload), and the unmasked image is kept in memory with the image just being re-masked rather than the entire object reloaded from disk.



On top of that, I added another nice feature for resizable objects - you can now have a "nine-slice" type resize, which recognizes corners and edges. I'm not sure of the normal term for this or how to describe it, so here's an image - notice the lack of the usual roughness updrafts have when resized vertically?

(The squasher trap does indeed lack a secondary animation at this point. The other trap, however, has one - it's just not noticable in a screenshot. The wheel always rotates, unless the trap has been deactivated.)

I have uploaded a new experimental build that includes both these features. Another thing to check out - try using clear physics mode in the new experimental build. :)
« Last Edit: May 16, 2019, 04:55:40 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Here's a new video, showing secondary animations in all the official graphic sets, and talking a bit about how they work. It shows off a nice selection of (though not even close to all of) the possibilities the new system has.

https://youtu.be/wutmgNuo-90

If anyone wants to request additional conditions / operations / etc (as long as they're not particularly complex), feel free to ask (though I make no promises) - I would like to hear proposed use cases, so I can consider how worthwhile any ideas are.
« Last Edit: May 05, 2019, 10:42:27 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline mobius

  • Posts: 2742
  • relax.
    • View Profile
I have a few artistic suggestions, and I'm willing to attempt the changes myself if that's okay but I won't  promise atm that I can.

Imo the flashing lights look a bit weird. Instead for the;
-pillar chain trap I suggest something like the top part turning or the chain swaying.
-marble trap; the top and/or bottom part could bob up and down like the icicle trap
-same for the brick squasher trap. Or since this one doesn't stick out as well; have some nob or needle thing bob up from below similar to the marble trap.
-pillar needle traps; the needles could protrude back and forth just a little bit.
everything by me: https://www.lemmingsforums.net/index.php?topic=5982.msg96035#msg96035

"Not knowing how near the truth is, we seek it far away."
-Hakuin Ekaku

"I have seen a heap of trouble in my life, and most of it has never come to pass" - Mark Twain


Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Whatever ideas you want to put forward, do keep in mind - it needs to be an animation that animates when the trap is idle; and stops, disappears or noticably changes when the trap is disabled. (Optionally, it can also stop, change or disappear when the trap is busy.) The stop / disappear can be "upon reaching frame 0", rather than instant.

This isn't because NL requires this as such, but rather because the whole point of using these is to more clearly show (a) that a trap is an interactive object of some kind, hence the animation when idle; and (b) that a trap is disabled, by some appearance that's different from the idle appearance.

As long as suggestions / proposals meet that criteria, I'm happy to consider them - and I'm also happy to help with actually making them work, as I can see how this system could be a bit intimidating at first (though it honestly is pretty simple once you get the hang of it).
« Last Edit: May 06, 2019, 01:17:16 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline IchoTolot

  • Global Moderator
  • Posts: 3605
    • View Profile
I would advice to only rely on the flashing lights when nothing else really works.

They look alright on the bear trap, but on the other ones they look very odd.

I agree with mobius proposals here (for the brick stomper a bobbing up and down I would say would be enough if executed right). The stopping of rotations/vibrations I would say is enough of a sign that a trap is disabled. And the killing animation + trap sound is enough to show that these traps are indeed busy.

Maybe discussing possible animations first and then implement would be best for the original tilesets ???. For the custom ones the creator should have the last word.

As a result of more and more animations that will trickle in I guess the "styles" download will need frequent updates during the time after this becomes stable.


I will most likely tackle the L2/L3 objects one at a time once this becomes stable --> adding more animations slowly as time progresses, but I still would be thankful for helping hands. I won't make a time-consuming marathon to get these out as quickly as possible.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
Maybe discussing possible animations first and then implement would be best for the original tilesets ???. For the custom ones the creator should have the last word.

Since these have zero effect on physics, I prefer a different approach: Make something that does the job; if something really good comes along, we can replace it later. Indeed, I might have done a few of these differently if I waited until the feature was in its current state - when I did the L1 ones (as opposed to ONML, which I did later), NL didn't support things like "hide the secondary animation when the object is triggered", so eg. a bobbing up and down wouldn't've been possible to make look good. But indeed, I'm open to accepting better ones if anyone's willing to make them - and as I've said, I'm quite happy to help with the "making it work" side of things.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Uploaded an experimental version update, commit 8b2a634. The biggest change is a bugfix, fixing a bug which caused flipped / inverted / rotated objects to disappear (at best) or crash NeoLemmix (at worst) for most object types. That aside, it includes a slight fix to the Brick style's wheel trap.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Uploaded a new experimental, commit 8e435a5. Fixes a minor bug (where "HIDE" as the default state for an object didn't work, unless the default animation state was also specified and wasn't "PLAY" or "MATCH_PRIMARY"). The new experimental also includes secondary animations for all traps and locked exits in Lemmings Plus and Doomsday Lemmings styles.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Uploaded a new experimental, commit 17cd97c. Aside from a few improvements / fixes here and there (and a few more secondary animations in my styles), it also contains an experimental editor version, EXP A397DAF. This editor version handles secondary animations, sort of - it gets NeoLemmix to render the composite image of the object. This does mean a bit of lag while loading styles (and possible black screen flashes - that's just NeoLemmix running very briefly). There's room to improve here - and some of that room-to-improve is already implemented on the NeoLemmix player side - but that's lower priority than just getting it working in general.

Please be warned - this experimental editor also includes Shimmier support; but the secondary anims experimental player does NOT have support for the shimmier. So the shimmier not working in this experimental is not a bug.

If you're trying out this experimental, please in particular report any instance in which an object with secondary animations - in particular, one with secondary animations that are outside the object's original boundaries - loads or saves in the editor at an incorrect position. So far, I haven't found any cases in which there's problems, but the potential is there due to how it's implemented so it's something I'd especially like anyone testing this to look out for.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
For the purpose of improving the load times - one thing I could do is not have NeoLemmix regenerate the images every time the editor runs, but rather, the editor keeps a saved copy of them.

However, this means that either (a) the editor needs to detect if the object has been changed; or (b) the user needs to manually delete the cached copy if the object changes.

The editor could try to detect changes by looking at modified dates of files. Checking this for the primary animation's PNG file and the metainfo file, which are the only parts that could affect physics, is easy enough. Checking for modifications to secondary animation graphics would be a bit trickier. So my thought here is - if changes to the NXMO file or primary animation PNG file are detected, regenerate the image. Otherwise, assume the cached copy is good. A content creator who wishes to force regeneration could either delete the cached copy, or just load and re-save (thus changing the modified date) the files. How does this sound?

That aside, NeoLemmix also has support for being asked to render multiple objects in a single run. Making the editor make use of this would hugely improve load times, it's just a fair bit of work - but I intend to try and do this.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Known bug in experimental (occurs in the editor, but I think it's the game's fault): If loading a level that uses pieces that rely on NeoLemmix rendering them, in a style that has not yet been loaded into memory, NeoLemmix gives a few error messages then hangs while trying to render, which in turn causes the editor to hang until either it or NeoLemmix is terminated.

Workaround: Before loading a level, switch to its style in the piece selector and display the objects. This loads them into memory, after which you should be able to load the level without issues.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Uploaded yet another experimental. This one actually performs pretty well now editor-side. Instead of requesting pieces on-demand from NeoLemmix, the first time the editor runs, it asks NeoLemmix to pre-render all pieces that have secondary animations or recoloring on the primary animations. These get saved in an "editor" folder, and next time the editor runs, it just loads them directly from there.

Obviously, this means changes to the pieces won't be reflected. One option is to delete the "editor" folder (or more precisely, the "render" subfolder inside it), at which point the editor will request a new render of all pieces next time it runs. Advanced users can also make NeoLemmix produce these renders via command line arguments:
Render all styles:        NeoLemmix.exe render -editor
Render specific style(s): NeoLemmix.exe render -editor [style 1] [style 2] [style 3] ...


So for the most part, the editor will display objects with secondary animations visible. There are some limitations:
- Displaying secondary animations is currently not supported on pickup skill objects
- Displaying secondary animations is currently not supported on one-way arrow objects
- Displaying secondary animations is not supported (and likely will never be supported) on resizable objects where the primary animation is nine-sliced
- Displaying secondary animations may be inaccurate for resizable objects, especially if the boundaries of the primary and secondary animations differ or the secondary animations are nine-sliced

Still, I think this is well and truly good enough for real-world usage. There's still likely a few bugs to iron out (I already know of one; trigger areas are inaccurate for objects with secondary animations that extend outside the primary's bounds), but I'm quite happy with the loading / displaying in general.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Nepster

  • Posts: 1829
    • View Profile
Sorry for being so late to the party, but I just now realized, how complex the system became. When I first read about this, it looked a lot easier...

What did we need?
Mainly idling animations for traps and idling animations for locked exits.

What of all the new features are actually needed for this?
- Secondary animation sprites as png
- One animation group within the nxmo file
- Attributes "NAME", "FRAMES", "OFFSET_X" and "OFFSET_Y" and possibly the definitions when to start/stop the animation

So what is not needed?
- I really see no need for nine-slicing
- Why do we want to have multiple secondary animations?

Finally: Why do I care?
1) I want to keep it as simple as possible. We culled so much to make NeoLemmix simpler to use and now we have dozens of options to define animations, that I currently don't understand. Sorry, but it's bad enough (though necessary) to have the .nxmo files, so they should at least be as easy to write and understand as possible. And this means having only the most important options.
2) It is very, very important for me to be able to use the editor without a NeoLemmix.exe next to it. Yes, for play-testing the NeoLemmix.exe is necessary, but this should be an exception. Now, we cannot even display levels without having a NeoLemmix.exe next to it!
The experimental editor version released by namida has this dependency, which goes explicitely against my wishes for the editor, as mentioned to him by PM.

How to move forward?
Let us take a step back and discuss, which of the new features we actually do need, and let's not add the others (or rather let them live in an unreleased git branch). I really hope that we can find a simpler solution and that the editor itself can determine how a gadget should look like.
PS: As I thought the secondary animations were implemented in a much simpler way and wasn't aware of namida's work until now, I added a purely editor-based solution (basically overlapping the first animation frames of the main animation and the secondary one (if available))

Offline mobius

  • Posts: 2742
  • relax.
    • View Profile
I agree with Nepster that things should be kept as simple as possible so they can be finished quickly and done in a neat matter and not become too convoluted.

-Locked exits: I have not seriously looked into this so forgive me if I'm misunderstanding here but why do we need idling animations for locked exits? Isn't it enough that a door is over the exit signifying that it's locked and when it unlocks the door opens (goes away)?

-I don't care if multiple secondary animations are implemented. One idle animation per trap seems enough for me. Again, I'm only saying this because I'd rather see more work go into other features which is just a personal wish of mine.
But the 3 different states as they are seems pretty good to me: idle, working, disarmed. (disarmed having no animation). When things are more uniform they are easier to understand.

-Question to Nepster: Why is it bad that you must have an exe next to your editor? Why would you want/need to have it anywhere else? Or are you simply saying that it's bad that the editor depend upon the exe?
everything by me: https://www.lemmingsforums.net/index.php?topic=5982.msg96035#msg96035

"Not knowing how near the truth is, we seek it far away."
-Hakuin Ekaku

"I have seen a heap of trouble in my life, and most of it has never come to pass" - Mark Twain


Offline Nepster

  • Posts: 1829
    • View Profile
-Locked exits: I have not seriously looked into this so forgive me if I'm misunderstanding here but why do we need idling animations for locked exits? Isn't it enough that a door is over the exit signifying that it's locked and when it unlocks the door opens (goes away)?
Until now we always have two sprites for locked exits: One actual exit object and the second one the flames on top. The idling animations would allow combinung these two sprites. So you would only place a single locked-exit object in the level and it would have the usual nice flames.

-Question to Nepster: Why is it bad that you must have an exe next to your editor? Why would you want/need to have it anywhere else? Or are you simply saying that it's bad that the editor depend upon the exe?
1) I do have several editor test applications, that don't have any NeoLemmix.exe next to them.
2) With more dependencies, you would have to ensure that compatible versions are installed. Currently you can use the editor with practically any new-formats NeoLemmix player. It is bad enough that current/older versions of the editor will no longer display objects as nicely as they currently do, but we don't have to make this situation even worse.
3) Finally, it is indeed some kind of abstract principle. But it's there for good reasons: At work we have multiple applications that communicate and work together - and our support now has lists about "which version works with which other version", because our customers regularly have problems running the system and it always has to do with some communication failure between the applications. That's why I really don't want to have even more inter-application communication than necessary - it creates more problems (that are extremely hard to debug!) than it solves.


Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
2) It is very, very important for me to be able to use the editor without a NeoLemmix.exe next to it. Yes, for play-testing the NeoLemmix.exe is necessary, but this should be an exception. Now, we cannot even display levels without having a NeoLemmix.exe next to it!

Latest experimental from post 1 (and latest source code in my repo) has a fallback for this - if NeoLemmix isn't present, it won't make the request, and will simply display only the primary animation of the object. It does this as a one-time thing on first run (and later runs, if the "editor/render" folder has been deleted by the user), not on a regular basis - you could even copy these files from another NeoLemmix install, and get the benefits of player-rendered pieces, without needing a copy of the player to be present.

WRT complicating the code - most of the important stuff is contained in its own classes. Also, Simon suggested in Discord that the system should be future-proof and allow for multiple animations, in case future objects end up needing more. So I did exactly that. Lots of flexibility for content creators is a bonus that results from this. This system can easily be updated in the future to give extra conditions, if objects are added where they're relevant. It already accounts for objects that might end up needing multiple secondary animations - indeed, I've already found use cases for this:
- default:pickup - Absorbing masks into the secondary anims system, instead of having both of them in effect (which seems silly, as masks are basically just recolored but otherwise simpler secondary animations), means two secondaries are needed for the two different color regions. This would also hold true for any future objects that need multiple recoloring - and while this is currently only used by default:pickup and the various default:owa_xxx objects, it's supported for literally any object of any type in any style.
- namida_basement:exit - This one is purely aesthetic, and could be done without multiple (or even any) secondaries, had it been done up front. The only way in which it depends on the secondary anims system is that it allows the glows to be outside the original boundaries of the object.
- namida_horror:exit_02 - This one previously didn't have any secondary-animation-via-second-object (just the unlock animation, otherwise it was static), but had a (non-animated) red or green light depending on unlock status. Now, the light glows - this needs one secondary anim for the red glow, and one for the green glow. As well as multiple-secondary support in general, this also relies on the conditional logic type stuff - which really is quite simple.

And this is just with "what could it be useful for on existing objects". There's even more potential for it on new objects, which can be designed with these capabilities in mind.

Quote
So what is not needed?
- I really see no need for nine-slicing

Okay, I'm going to disagree with you really hard on this one. If anything, I would rather ditch the complex secondary anims and keep the nine-slicing feature. As a particularly strong case, take a look at the updrafts in namida_machine. To get the proper visual appearance, any updraft region using that style's updraft (assuming it doesn't intersect indestructible terrain or level boundaries on one or more sides) previously needed nine objects in the level, of six different object types (with the last three being the same object as another three, but flipped horizontally). With nine-slicing, this can now be done as a single object, and retain perfect visual appearance. The default updraft, and the fire objects in namida_machine, have also benefitted from this to a lesser extent. Further uses for this could allow for nice vertical resizing of water objects, too, rather than having to paste multiples on top of each other - I just haven't got around to actually doing this yet. Essentially - any resizable object that should have a clearly-defined edge, benefits hugely from this feature.

I would perhaps agree that there's no real need for secondary animations to support it, but with the current implementation, it would be more code for secondaries to not support it.

I would also agree that it should really have been implemented as a separate feature that in turn is supported in the animation system, instead of being implemented together with this animation system, but I approached this as a general "improve the object rendering" and thus did both at the same time.
« Last Edit: May 13, 2019, 04:26:07 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
The experimental editor in the exp version has been updated. The update contains a couple of bug fixes:
- Trigger areas displayed are incorrect for rotated objects, if the object is resizable in only one direction (unrelated to this feature, but no reason not to pull the fix into it)
- Trigger areas displayed are incorrect for player-rendered objects, if the object's secondary animation extends outside the primary animation's boundaries
- Nine-sliced objects are not sliced correctly if flipped / inverted / rotated, unless the slicing is symmetrical.

Nepster has written code which, as an alternative to my get-NeoLemmix-to-render code, attempts to display the first frame of the secondary animation ("the" because his code only supports one single secondary animation). I intend to (but haven't yet) integrate this into this experimental, so that we have both features - it'll get NeoLemmix to produce a good render if available, and if NeoLemmix isn't present or the specific object's render isn't there, it'll use Nepster's system as a better fallback than the primary animation alone. Not sure what the best way is to handle determining which secondary to show if there's more than one, this might have to be specifically stated in the NXMO file in the case of more than one secondary animation.

EDIT: I've also updated the exp copy of the player. Of particular interest, as Nepster feels the Shimmier is more or less ready to go for the next stable version, I see no reason not to include Shimmier support in this experimental. As such, the latest secondary-anims experimental now also includes Shimmier support. If upgrading from a previous secondary-anims experimental, make sure to re-extract the "default" style, as the pickup skill graphic has been updated to include the Shimmier, and it also includes necessary graphics for the panel and lemming sprites.

SECOND EDIT: I've integrated Nepster's secondary anim loading code as a fallback when no pre-render of the piece exists. This could occur if the piece was created (or had a secondary animation added) more recently than when the editor requested a build of the animations, or if NeoLemmix isn't present or is a version that doesn't support rendering. (Todo: Make the editor not even request the render in this latter case. It already doesn't attempt to request a render if NeoLemmix is not present.)
« Last Edit: May 13, 2019, 03:22:54 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Nepster

  • Posts: 1829
    • View Profile
WRT complicating the code - most of the important stuff is contained in its own classes. Also, Simon suggested in Discord that the system should be future-proof and allow for multiple animations, in case future objects end up needing more. So I did exactly that. Lots of flexibility for content creators is a bonus that results from this. This system can easily be updated in the future to give extra conditions, if objects are added where they're relevant. It already accounts for objects that might end up needing multiple secondary animations - indeed, I've already found use cases for this:
- default:pickup - Absorbing masks into the secondary anims system, instead of having both of them in effect (which seems silly, as masks are basically just recolored but otherwise simpler secondary animations), means two secondaries are needed for the two different color regions. This would also hold true for any future objects that need multiple recoloring - and while this is currently only used by default:pickup and the various default:owa_xxx objects, it's supported for literally any object of any type in any style.
- namida_basement:exit - This one is purely aesthetic, and could be done without multiple (or even any) secondaries, had it been done up front. The only way in which it depends on the secondary anims system is that it allows the glows to be outside the original boundaries of the object.
- namida_horror:exit_02 - This one previously didn't have any secondary-animation-via-second-object (just the unlock animation, otherwise it was static), but had a (non-animated) red or green light depending on unlock status. Now, the light glows - this needs one secondary anim for the red glow, and one for the green glow. As well as multiple-secondary support in general, this also relies on the conditional logic type stuff - which really is quite simple.

And this is just with "what could it be useful for on existing objects". There's even more potential for it on new objects, which can be designed with these capabilities in mind.
I have nothing against future-proof extensible design. But it should be easy to understand and use. And given my problems to understand why we need nine-slicing, when to use it or even how to create a decent first frame for the editor, the current design is not easy to understand.
As soon as we have a system, where I don't have to look at the NL player rendering code to find out what the first (idling) frame should look like, but can determine this purely from the posts here and the .nxmo/.png files, I am happy. In that case I will gladly implement that rendering in the editor, to avoid the dependency to the NL player completely.

Quote
So what is not needed?
- I really see no need for nine-slicing

Okay, I'm going to disagree with you really hard on this one. If anything, I would rather ditch the complex secondary anims and keep the nine-slicing feature. As a particularly strong case, take a look at the updrafts in namida_machine. To get the proper visual appearance, any updraft region using that style's updraft (assuming it doesn't intersect indestructible terrain or level boundaries on one or more sides) previously needed nine objects in the level, of six different object types (with the last three being the same object as another three, but flipped horizontally). With nine-slicing, this can now be done as a single object, and retain perfect visual appearance. The default updraft, and the fire objects in namida_machine, have also benefitted from this to a lesser extent. Further uses for this could allow for nice vertical resizing of water objects, too, rather than having to paste multiples on top of each other - I just haven't got around to actually doing this yet. Essentially - any resizable object that should have a clearly-defined edge, benefits hugely from this feature.
Ok, slowly but surely I start to understand what nine-slicing is about. But this seems to me like a completely different use-case than secondary animations, or am I mistaken there? Secondary animations are for switching animations depending on the object state, while nine-slicing is about nice looking edges of resizable objects.
If this take on secondary animations vs. nine-slicing is somewhat correct, could we please split the discussions (and feature branches)? I think I would have appreciated nine-slicing a lot more, if you had it introduced it in a thread "Nine-slicing: Resizable objects with nice edges" instead of here in "Secondary animations for triggered objects". 

I would perhaps agree that there's no real need for secondary animations to support it, but with the current implementation, it would be more code for secondaries to not support it.
I now wonder: Does NL first merge the secondary animations into the primary one and then apply the nine-slicing, or the other was around, i.e. first apply nine-slicing to both animations and then merge them?
I see reasons for both: The second one is more flexible, but it's easy to forget to add the nine-slicing parameters although one probably always wants to have them.

I would also agree that it should really have been implemented as a separate feature that in turn is supported in the animation system, instead of being implemented together with this animation system, but I approached this as a general "improve the object rendering" and thus did both at the same time.
Yeah, see my comments above. There has been quite a bit of confusion about this on my part.

Offline Strato Incendus

  • The King of Shimmiers (crowned by Flopsy ;D )
  • Posts: 1738
  • #RIP Spearer/Grenader (2020 - 2021)
    • View Profile
Just a layman question: namida told me this is a feature NeoLemmix needs (in the sense of "can't live without it").

By that I understand "something that affects the rules / game mechanics" of Lemmings.

So far, most of the uses for this feature seem to serve aesthetic purposes. I do like the idea of being able to differentiate between armed and disarmed traps (without having to use clear physics, that is)! :thumbsup: Although I thought this could also be done in a similar way as with locked exits and their respective buttons, which simply have two different static graphics for each of their possible states.

Are there any other gameplay-relevant uses for this?

Not meaning to devalue your work here! :) It just seems curious to me that triggered animations were removed but secondary animations for triggered objects seem to be regarded as an actual requirement now.
My packs so far:
Lemmings World Tour (New & Old Formats), my music-themed flagship pack, 320 levels - Let's Played by Colorful Arty
Lemmings Open Air, my newest release and follow-up to World Tour, 120 levels
Paralems (Old Formats), a more flavour-driven one, 150 levels
Pit Lems (Old Formats), a more puzzly one, 100 levels - Let's Played by nin10doadict
Lemmicks, a pack for (very old) NeoLemmix 1.43 full of gimmicks, 170 levels

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
I now wonder: Does NL first merge the secondary animations into the primary one and then apply the nine-slicing, or the other was around, i.e. first apply nine-slicing to both animations and then merge them?

NL never merges the animations at all. Instead, it essentially renders each one as if it were a separate object, offset by so-and-so from the primary animation's position. The end result, in terms of nine-slicing, is equivalent to your latter scenario.

Quote
As soon as we have a system, where I don't have to look at the NL player rendering code to find out what the first (idling) frame should look like, but can determine this purely from the posts here and the .nxmo/.png files, I am happy. In that case I will gladly implement that rendering in the editor, to avoid the dependency to the NL player completely.

To reduce it to "only what the editor needs to know about". I am including here in some cases, details of how NeoLemmix would handle situations that should never actually occur, or how some common possible cases of invalid input might be handled.

Order to render, back to front, is lowest Z_INDEX to highest Z_INDEX. If Z_INDEX is not specified, it's assumed to be 1 for the primary animation, and 0 for any other animation. In the case of two animations with equal Z_INDEX value, primary animation is rendered first if it's one of them, after that (or if it isn't) the rendering order is determined by which one comes first in the file.

To load animation data - primary animation comes from a $PRIMARY_ANIMATION section or the main section, all others from a $ANIMATION section.

Primary animation in the main section (Ignore ALL of these if a $PRIMARY_ANIMATION segment exists)
FRAMES - frame count, integer, assume 1 if not present
HORIZONTAL_STRIP - sprite strip is horizontal if present, vertical if absent
INITIAL_FRAME - self-explanatory, -1 = random. Ignored if the current frame number is physics-significant (traps, entrances, etc).
CUT_LEFT - How many pixels on the left of the primary animation are considered to be part of the edge for nine-slicing
CUT_TOP, CUT_RIGHT, CUT_BOTTOM - Ditto for the respective sides
PREVIEW_FRAME - same as INITIAL_FRAME, treat as deprecated
RANDOM_START_FRAME - same as "INITIAL_FRAME -1", treat as deprecated


Primary animations in a $PRIMARY_ANIMATION segment
FRAMES - same as above
NAME - suffix on the piece name for the image file, eg. if this is "primary" on an object "bob", the primary animation comes from "bob_primary.png". If this is empty, no suffix (not even the underscore) is appended, so on a piece called "bob", empty (or no) NAME would load the image from "bob.png".
COLOR - name of a color (from the theme file) to recolor the primary animation with, see default:owa_left (or any other direction) for example of this on primary anim
HORIZONTAL_STRIP - same as above
Z_INDEX - determines Z index, as explained in rendering order (default = 1 for primary anim, 0 for any other anim. Negative values are valid but decimal values currently are not - though maybe they should be?)
INITIAL_FRAME - same as above (note, the deprecated PREVIEW_FRAME / RANDOM_START_FRAME do not work here)
OFFSET_X, OFFSET_Y - offset from physics position of the object to draw the primary anim at (in normal orientation)
CUT_LEFT / TOP / RIGHT /BOTTOM - same as above

Secondary animations ($ANIMATION segment) - Everything from the $PRIMARY_ANIMATION segment, plus:
HIDE - don't render this animation (unless overridden by a trigger, see below), unless ((initial frame != 0) and (state != stop or pause)) or (state = match-primary-animation-frame)

These next few are mutually exclusive, this is the order from highest to lowest priority if more than one exists:
> PAUSE - Editor does not need to do anything special here (beyond interaction with "HIDE" above)
> STOP - INITIAL_FRAME should be ignored, instead rendering as frame 0 (in NL, "STOP" has the effect of setting the current frame to 0, then immediately changing state to PAUSE)
> LOOP_TO_ZERO - for editor purposes this can be treated as equivalent to "PLAY" if (initial frame != 0), or "PAUSE" if (initial frame == 0)
> MATCH_PRIMARY_FRAME - ignore INITIAL_FRAME, render as same frame number as the primary animation is on
> PLAY - Editor does not need to do anything special here (beyond interaction with "HIDE" above)
> If none of the above are present - this is treated as "PLAY", except if the line "HIDE" is present, in which case it's treated as "STOP" (to avoid "Why is my animation not hiding? I put hide there!" "Because you need STOP too")

$TRIGGER subsections - these are used to define the different conditions for animations to appear or disappear. Just to be clear (in case the layout of this message is confusing) - $TRIGGER only works for secondary animations! These subsections can contain the following requirements, with either "true" or "false". If a requirement is not specified, then the trigger does not depend either way on that requirement. Remember - these explanations are just for editor purposes (and exclude a few edge cases), the game has more detail to it than this. If multiple $TRIGGER sections exist, the last one (in file order) where all conditions are met, is the one that takes effect.
FRAME_ZERO - "True" for infinite-use trap, teleporter, receiver, splitter pointing left, window*. Otherwise, "false".
FRAME_ONE - "True" for pickup skill, locked exit, unlock button, splitter pointing right, or single-use trap. Otherwise, "false".
BUSY, TRIGGERED, DISABLED - Always "false" for editor purposes.

* assuming the editor retains "render windows in open state". If it renders them in closed state, FRAME_ZERO is false and FRAME_ONE is true.


STATE - This can have a value of "STOP", "PAUSE", "PLAY", "LOOP_TO_ZERO" or "MATCH_PRIMARY_ANIMATION". These all work the same way as the equivalent lines in the main part of the $ANIMATION section, but note they're values in a line with a key of "STATE" here, rather than their own lines. (Yeah, this is weird. I should change this to be consistent - which way do you think is better?)
HIDE - Works the same way as in the main $ANIMATION section. Just like there, absence of "HIDE" is treated as "show", there's no explicit "SHOW" keyword.

As in the main part, too, the absence of a "STATE" line is treated as equivalent to "STATE PLAY", except if "HIDE" is present, in which case it's treated as "STATE STOP". However - "STATE" with an invalid parameter, regardless of the presence of "HIDE", is always treated as "STATE PLAY".

Any case-sensitivity in NeoLemmix in any of this, is a bug, so feel free to treat it all as case-insensitive. Behaviour of "MATCH_PRIMARY_FRAME" when primary anim's current frame number is higher than the amount of frames the secondary has, is (probably) that NeoLemmix crashes - up to you how accurately you want to replicate that, I suggest don't put too much effort into it as this behaviour may change in future commits to something more sane like "display the last frame" or "display nothing".

Some possible "gotchas":
- "COLOR" is valid in $PRIMARY_ANIMATION
- "OFFSET_X" and "OFFSET_Y" are valid in $PRIMARY_ANIMATION
- Empty / absent name is valid in $ANIMATION (the secondary animation would load from the non-suffixed PNG file in this case)
- Multiple animations with the same NAME is valid (see: namida_basement:exit for an example of this), both animations would just use the same graphic (they could still differ in any other params!)
- Again, watch out for "INITIAL_FRAME -1". Entirely up to you how you actually handle that - whether you want to render a random frame, or perhaps just render it as frame 0 or something.
« Last Edit: May 13, 2019, 10:33:23 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
By that I understand "something that affects the rules / game mechanics" of Lemmings.
...
Are there any other gameplay-relevant uses for this?

This is mostly an aesthetic feature, with gameplay influence limited to that well-designed use of secondary animations can help give the player more information on the state of the level / the objects in it. Let's take for example, the weed trap in the Rock set - someone unfamiliar with the set could mistake it for terrain and not even check clear physics mode. That won't happen when it's got an animation while idle.

There's also potential future use for it. I'm currently working on a feature that uses a specially-made secondary animation to allow for custom digits for pickup skill counts - and the biggest reason for this, is that I intend to then also use this same digit animation feature for limited-lemming exits / entrances. As an actual animation, this would be hidden - and then the animation frames of it are rendered as the digits. (Sure, they don't need it, but they'll sure as hell be able to look a lot nicer with this.)

I was actually quite surprised to see triggered animations had been removed. Unlike radiation / slowfreeze, they don't complicate gameplay, and 99% of the code needed for them to function is needed anyway for other object types that still exist (most notably traps, which are essentially just a triggered animation that also removes a lemming). And especially with the secondary animations feature, there's probably a lot of artistic potential in them, too...
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Nepster

  • Posts: 1829
    • View Profile
[explanations on animations]
Ok, that's a lot of information and I am pretty sure I am still misunderstanding half of it. I have a few questions, but almost certainly more will appear once I start implementing the editor-side rendering in earnest:
1) Why do we have to distinguish between primary animations and secondary ones? It seems that the secondary animations are even more flexible, and the only advantage of primary animations is, that they override certain attributes. But then the style designer should not set the attributes in the first place.
2) As the STATEs seem to be (and should be) mutually exclusive, they should be set as an attributes, i.e. like "STATE PLAY". If we have stuff like "LOOP_TO_ZERO" as keywords, that would indicate to me, that it can be combined with the "PLAY" or "PAUSE" state for example.
3) Why do we need "Z_INDEX"? The default specifies a certain order anyway, which is pretty natural, so I don't see any need for this attribute.
4) Why do we need "HIDE" as an option for secondary animations? If we are not displaying anything, then why have this secondary animation at all?
5) Regarding "INITIAL_FRAME": At what point did we deprecate "RANDOM_START_FRAME" and introduced the "-1" instead? I would have preferred to add "INITIAL_FRAME RANDOM" instead. (Might very well be, that it was me who introduced the "-1" and I am criticizing myself here...)
6) I am still unsure which of the STATEs and the TRIGGER types are actually necessary for the most common use-cases (i.e. idling traps and locked exits), and which are just for potential future use? Can we stick to the absolute essentials for now? This would help people like me to get used to this.

I was actually quite surprised to see triggered animations had been removed. Unlike radiation / slowfreeze, they don't complicate gameplay, and 99% of the code needed for them to function is needed anyway for other object types that still exist (most notably traps, which are essentially just a triggered animation that also removes a lemming). And especially with the secondary animations feature, there's probably a lot of artistic potential in them, too...
The main reason is player expectations: If something triggers, then this signals a game physics effect on a lemming. With the various gadget types it is hard enough to newer players to learn the various effects NL supports. No need to confuse them with no-effect effects ;). This all stems directly from my own experience as a player: Such objects have been very rare, so when I first encountered it, I wasn't even aware that NeoLemmix supported triggered animations. So I tried for half an hour to find out, what this gadget actually does and never found anything! :devil::devil::devil:
PS: I actually would prefer an even stronger rule, namely: If something moves, it either is currently interacting with a lemming or has at least the potential for it. Unfortunately moving background pieces break that rule (which is why I wanted to remove them at some point)...

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
1) Why do we have to distinguish between primary animations and secondary ones? It seems that the secondary animations are even more flexible, and the only advantage of primary animations is, that they override certain attributes. But then the style designer should not set the attributes in the first place.

Primary animation is tied to physics, same way that the lone animation up until now has been. Aside from that, they are indeed identical (outside of NL rejecting a few specific settings on the primary) - the primary animation is loaded by the same code as the secondaries just with a few properties being overridden. Rendering also uses the same code as primaries, though frame update does not[b/] - the primary is perpetually in a "paused" state, with the frame number being directly manipulated by game physics.

Quote
2) As the STATEs seem to be (and should be) mutually exclusive, they should be set as an attributes, i.e. like "STATE PLAY". If we have stuff like "LOOP_TO_ZERO" as keywords, that would indicate to me, that it can be combined with the "PLAY" or "PAUSE" state for example.
I noted above that it seems weird to have them defined as single-line keywords in one place, and values of a "STATE" keyword in another. Good point here; let's make it "STATE _____" in both cases.

Quote
3) Why do we need "Z_INDEX"? The default specifies a certain order anyway, which is pretty natural, so I don't see any need for this attribute.
Currently, the primary animation is treated as coming first in file order regardless of where it's actually placed. Z_INDEX allows placing in front of OR behind it. We would need to change how the primary animation is defined, to continue allowing for this without a Z_INDEX - relying on its order in the file, since it uses a $PRIMARY_ANIMATION tag instead of an $ANIMATION, would violate the "lines / sections are not order-sensitive in relation to lines / sections with different keywords" rule, and would also need a rule on where in the Z order it goes if specified in the main segment (for backwards compatibility, or just because it's an object with only one animation, or any other reason).

Quote
4) Why do we need "HIDE" as an option for secondary animations? If we are not displaying anything, then why have this secondary animation at all?
Reason 1 - future animations that don't get used directly. For example, digits for a limited-count exit / entrance.
Reason 2 - animations that don't display in default state, but do when one of the triggers is fulfilled. For an example of this, see namida_horror:exit_02. Or the inverse - displaying usually, and hiding when a condition is fulfilled - which namida_horror:exit_02 also provides an example of.

Quote
5) Regarding "INITIAL_FRAME": At what point did we deprecate "RANDOM_START_FRAME" and introduced the "-1" instead? I would have preferred to add "INITIAL_FRAME RANDOM" instead. (Might very well be, that it was me who introduced the "-1" and I am criticizing myself here...)
No, this was something I did during creating this system. Yeah, "INITIAL_FRAME RANDOM" could be a special case that's interpreted as equivalent to -1. I'd say similar logic to in your point #2 applies here - don't have it as an entirely separate command either way.

Quote
6) I am still unsure which of the STATEs and the TRIGGER types are actually necessary for the most common use-cases (i.e. idling traps and locked exits), and which are just for potential future use? Can we stick to the absolute essentials for now? This would help people like me to get used to this.
I've already found use cases for all of these (except, IIRC, "TRIGGERED", but there are differences player-side between this and "BUSY"*), though some are definitely more common. But as a few examples:
- The common blinking-light makes use of the instant-stop (vs loop_to_zero), and the "BUSY" condition (light stops blinking while trap is killing) and "DISABLED" condition (light stops blinking when trap disarmed).
- The "bob up and down / side to side when idle" effect, eg. ohno_snow:trap, requires hiding secondary animations on a BUSY (or TRIGGERED) condition to avoid really weird visuals when triggered, and the "PAUSE" (without going back to frame zero) so the position won't suddenly jump by a couple of pixels (depending on current frame) when the trap gets disarmed.
- For an example of why the absence of these can be important, see ohno_brick:trap_02. The secondary animation on that one only pauses (remaining on its current frame) when the trap is disarmed, not in any other case.

A post earlier in the topic, which I've mostly been keeping updated with new exp's, details in full how this new system works.




Quote
The main reason is player expectations: If something triggers, then this signals a game physics effect on a lemming.
Counter-examples: Unlock buttons, pickup skills, locked exits. None of their animations directly affect a lemming physics-wise. Counter-counter-point: ...although they do all indicate something physics-related at least.

Quote
With the various gadget types it is hard enough to newer players to learn the various effects NL supports. No need to confuse them with no-effect effects ;). This all stems directly from my own experience as a player: Such objects have been very rare, so when I first encountered it, I wasn't even aware that NeoLemmix supported triggered animations. So I tried for half an hour to find out, what this gadget actually does and never found anything! :devil::devil::devil:
We have clear physics mode nowdays; and an up-to-date NeoLemmix Introduction Pack should help here too. I did start working on something, but then got bored with it for now.

Quote
PS: I actually would prefer an even stronger rule, namely: If something moves, it either is currently interacting with a lemming or has at least the potential for it. Unfortunately moving background pieces break that rule (which is why I wanted to remove them at some point)...
Clear physics mode, and (maybe?) the "hide background" options deal with those situations. (Triggered animations could perhaps be included in that option too, maybe a general "remove visual fluff" option? I suspect few, if any, users will feel the need to be able to pick-and-choose on a finer level here.). NeoLemmix has always tried to account for both the puzzle side and the artistic side of creativity; triggered animations and moving backgrounds are both beneficial to this, and the full extent of secondary animations will be helpful to that end too.



Perhaps we should approach this as a joint effort - you know how you like things to be loaded, etc; while I understand in full how the system works, down to the technicalities / etc. Perhaps you can write the code as you'd like it to be laid out / etc, with a call to a dummy "GetThisAnimFrameNumber" function for any secondary animations (for the primary animation, continue to handle it the way you do now, aside from paying attention to INITIAL_FRAME on any object where current frame is not physics-significant). Then, I'll actually write the GetThisAnimFrameNumber function. "GetThisAnimFrameNumber" should be passed the following params:
- Gadget type
- Current frame number of primary animation
- Value of INITIAL_FRAME
- The STATE (out of "PLAY", "PAUSE", "LOOP_TO_ZERO", "STOP", "MATCH_PRIMARY_FRAME", <other> or <absent>) and HIDE (out of <present> or <absent>) in the secondary anim's main section
- Any $TRIGGER sections, in file order, the conditions (keys {"FRAME_ZERO", "FRAME_ONE", "BUSY", "TRIGGERED", "DISABLED"} with values "TRUE", "FALSE", or <absent>), and STATE and HIDE (same as for main section)

If you'd rather write this yourself, the following C#-ish pseudocode will produce correct results for the frame to display. As before, this only operates to the extent of "what the editor needs to know about".

- If result is "RESULT_HIDE", don't display the frame at all.
- If result is "RESULT_RANDOM", display a random frame.
- If result is "RESULT_HIDE_OR_RANDOM", either hide or display a random frame other than frame 0. This result only comes up in a single edge case that shouldn't actually happen in real-world situations.
** This pseudo-code is based on the assumption we change from different keywords, to key "STATE" + value ["STOP", "PAUSE", etc] in the base $ANIMATION section. It also includes an oddity fix that I plan to (but haven't yet) implemented, namely for that "STATE <invalid input>" is different from no "STATE" line at all.
** Commented-out lines in the conditionals, are only needed to account for edge cases.

Code: [Select]
bool DoesConditionPass(bool conditionResult, enum conditionRequirement)
// enum values: "TRUE", "FALSE", <not present>
{
  switch (conditionRequirement)
  {
    case "TRUE": return conditionResult;
    case "FALSE": return !conditionResult;
    case <not present>: return true;
  }
}

int GetAnimationDisplayFrame(params)
{
  const int RESULT_HIDE = -1; // actual value doesn't matter, just needs to be a unique negative number
  const int RESULT_RANDOM = -2;
  const int RESULT_HIDE_OR_RANDOM = -3;
 
  bool hideIfAllowed;
  enum state; // enum values: "PLAY", "PAUSE", "STOP", "LOOP_TO_ZERO", "MATCH_PRIMARY_FRAME", <other / not present>

  foreach ($ANIMATION + all "$TRIGGER"s inside it) // $ANIMATION first, then each $TRIGGER in file order
  { 
    if (this is a $TRIGGER, not the base $ANIMATION)
    {
      bool conditionsPassed = true;
     
      // FRAME_ZERO
      bool thisConditionState = true;

      if (<gadget type> is not one of [infinite-trap, /*pickup, locked-exit,*/ teleporter, receiver, splitter, hatch])
      or (<gagdet type> is splitter && gadget is facing right (ie: frame 1))
  //  or (<gadget type> is pickup && gadget has a skill assigned (ie: not frame 0))
  //  or (<gadget type> is locked exit && level contains unlock buttons (ie: exit should display as frame 1))
        thisConditionState = false;
     
      if (!DoesConditionPass(thisConditionState, $TRIGGER."FRAME_ZERO"))
        Continue; // the forEach loop
     
      // FRAME_ONE
      bool thisConditionState = true;

      if (<gadget type> is not one of [pickup, locked-exit, button, splitter, single-trap])
      or (<gagdet type> is splitter && gadget is facing left (ie: frame 0))
  //  or (<gadget type> is pickup && gadget has no skill assigned (ie: frame 0))
  //  or (<gadget type> is locked exit && level lacks unlock buttons (ie: exit should display as frame 0))
        thisConditionState = false;
     
      if (!DoesConditionPass(thisConditionState, $TRIGGER."FRAME_ONE"))
        Continue; // the forEach loop
       
      // TRIGGERED
      if (!DoesConditionPass(false, $TRIGGER."TRIGGERED"))
        Continue;
       
      // BUSY
      if (!DoesConditionPass(false, $TRIGGER."BUSY"))
        Continue;
       
      // DISABLED
      bool thisConditionState = false;
     
  //  if (<gadget type> is teleporter && <no receivers exist in level>)
  //  or (<gadget type> is receiver && <no teleporters exist in level)
  //    thisConditionState = true;

      if (!DoesConditionPass(thisConditionState, $TRIGGER."DISABLED"))
        Continue;
      }
    }
   
    hideIfAllowed = $TRIGGER."HIDE".IsPresent?; // or $ANIMATION, as applicable
    state = $TRIGGER."STATE"; // or $ANIMATION, as applicable.

    if (state = <other / not present>)
    {
      if (hideIfAllowed)
        state = "STOP";
      else
        state = "PLAY";
    }
  }
 
  if (hideIfAllowed)
  {
    if (state is one of [STOP, PAUSE])
      return RESULT_HIDE;
     
    if (state is LOOP_TO_ZERO && $ANIMATION."INITIAL_FRAME" = 0) // INITIAL_FRAME being absent counts as zero here
      return RESULT_HIDE;
     
    if (state is LOOP_TO_ZERO && $ANIMATION."INITIAL_FRAME" < 0)
      return RESULT_HIDE_OR_RANDOM; // edge case.
  }
 
  switch (state)
  {
    case "PAUSE", "PLAY", "LOOP_TO_ZERO": // note that <other / not present> is already changed
                                          // to either "PLAY" or "STOP" by this point
      if ($ANIMATION."INITIAL_FRAME" < 0)
        return RESULT_RANDOM;
      else
        return $ANIMATION."INITIAL_FRAME";
       
    case "STOP":
      return 0;
     
    case "MATCH_PRIMARY_FRAME":
      return <primary anim's frame>;
  }
}

I've generally found, so far, that - for the most common cases of a single secondary anim - single-use traps generally need three conditions (one for BUSY, one for DISABLED, one for FRAME_ZERO (ie: used up)), while infinite traps usually need one or two depending on the exact nature of the animation, and locked exits usually don't need any (though namida_horror:exit_02, which has a somewhat different from usual animation setup, uses two).
« Last Edit: May 14, 2019, 12:09:51 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
After further thought (but no implementation, yet), perhaps we can simplify the conditions even further - "IDLE", "BUSY" and "DISABLED". All cases of FRAME_ZERO or FRAME_ONE can be folded into either "IDLE" or "DISABLED" (whichever is more applicable), while "TRIGGERED" would be folded into "BUSY" (with teleporters / receivers retaining the current behaviour of Busy, and everything else having the behaviour of Triggered). I suspect that the few cases this would directly break, could be re-implemented with careful ordering.

These conditions would be mutually exclusive, so in turn, triggers could contain a "CONDITION _____" line, instead of the current "<condition> <TRUE/FALSE>" line. Priority would remain indicated by the order in file.

I really do feel all the current values for STATE are necessary, though.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Simon

  • Administrator
  • Posts: 3851
    • View Profile
    • Lix
Quote from: Nepster
If something moves, it either is currently interacting with a lemming or has at least the potential for it.

:thumbsup:

I have pushed for more than 2 animations per gadget because of an even stronger variant of this guideline: What moves, can interact, and what does not move, is terrain.

Not all animations classify into idle or busy. I'd rather not associate "primary" with busy and "secondary" with idle (exits feel more idle than busy, yet have only a primary animation).

Quote from: namida
- For a teleporter (A) linked to a receiver (B), "TRIGGERED" is true for A while A is animating, and true for B while B is animating; at other times, it is "false". "BUSY" is true for both A and B while either of them is animating, and "false" at other times.

Hmm, I haven't thought about such a very strong link between two gadgets' states/animations. This breaks a 1:1-association between states and animations. I'll read everything again before advising further; I'd really like to keep all physics intact.

-- Simon

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
Hmm, I haven't thought about such a very strong link between two gadgets' states/animations. This breaks a 1:1-association between states and animations. I'll read everything again before advising further; I'd really like to keep all physics intact.

Teleporters and receivers are already co-dependent. If a receiver is busy, the corresponding teleporter cannot be used. Prior to a change requiring one teleporter = one receiver (before this, it was possible for multiple teleporters to link to the same receiver, though not the reverse), the inverse also applied - if one teleporter is busy, the receiver (and thus, any other teleporters linked to it) cannot be used. Thus, to me, it makes sense for any animation change while the teleporter / receiver is busy, to extend to when the paired device is busy too.

Quote
Not all animations classify into idle or busy. I'd rather not associate "primary" with busy and "secondary" with idle (exits feel more idle than busy, yet have only a primary animation).

Currently, the rule here is: If there is an animation which is directly tied to physics in some way (such as a trap's killing animation), it is always the primary animation. I would expand this rule a bit for futureproofing - "If there are any animations which are directly tied to physics in some way, whichever one of these is relevant first in a typical use case, is the primary animation." I already have a WIP case where this distinction would matter - the limited-lemming-count exits, a locked exit could use a second physics-relevant animation for closing again. (This can be optional for locked exits - if no closing anim is present, play the opening anim in reverse. But I would very much like the support to be there.)

However - the "IDLE", "BUSY" and "DISABLED" flags (using the simpler proposal above) are not related to directly specifying the animations, but rather, for some basic conditional logic on an animation. You can pause or hide (or conversely, play or show) an animation based on these flags, but you could also have multiple animations that aren't tied to any of them - perhaps for parts that don't loop with the same frequency, or just to simplify graphic creation by splitting it into a couple of parts, or perhaps even just to expand an object's graphic outside the original boundaries of the object.

I have been wondering if having a small number of pre-set selections (that automatically set up triggers based on a few common use cases) might not be a bad idea, though. I would still like to support the full system - I suspect the back-end code is better off keeping most of the flexibility, so any change would really just be "we're intentionally withholding some of the capability from users". And I think the real potential of this system will only become apparent when new content, created with it in mind, is released...
« Last Edit: May 14, 2019, 08:17:28 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Okay, I've reviewed (but not yet implemented) some possible changes to simplify this. With this system, it should be possible for every trigger to have just a single "is this TRUE?" condition, as for the most part these are mutually exclusive. The few cases this does rule out that were possible before, should still be doable by use of multiple secondary animations. I'll see if I can indeed replicate all secondary-anim effects I've made so far using this system, after implementing the new conditions but before the change to one-condition-per-trigger. Even without that change, in practice most triggers would still only ever use a single condition.

Almost all cases are covered by the first three conditions - "READY", "BUSY" and "DISABLED". The last three are only for a few specific objects to use, that may have states that we want to be able to distinguish but aren't completely covered by the main conditions. Many object types, that would in practice have no reason to ever utilize these conditions in the first place, have sensible fallback values (basically, whatever result would come from applying the general rule) set so there's a defined behaviour if a condition is specified.

Those with a (?), I'm not 100% sure about. And the "gatc" prefix is just an internal-use thing in the code, it has nothing to do with how any of this would be represented in data files.

Spoiler (click to show/hide)

EDIT: Working on implementing this, in a separate branch in case I feel I'd rather stick to the original system (or a hybrid - possibly new conditions, but old support for condition mixing; but I only want to go back to this if it's really necessary). In regards to the edge cases in the above - I've decided to keep DOM_NONE and DOM_BACKGROUND consistent with the default for their group; but I did implement the unpaired teleporter / receiver edge case simply because the code is simpler to implement this than it is to not implement it (it gets picked up by the same test that checks for disarming on traps).

EDIT: I can now confirm, everything I did in the official and my styles combined under the previous system, remains 100% possible under this system. Single-use traps needed a bit of reordering of the trigger conditions, but everything else just needed updating to the new equivalent conditions. No images needed modification.



Under the new code, the editor needs to be aware of the following trigger conditions (all specified in the format of a single line "CONDITION ______" inside the trigger - multiple conditions or testing for "false" is no longer supported). As always, these values are "only as far as the editor needs to know":

"READY" - False for locked exits (edge case: unless no buttons exist in the level), otherwise true (edge case: false for teleporters and receivers if no object of the other type exists in the level)
"BUSY" - Always false
"DISABLED" - True for locked exits (edge case: unless no buttons exist in the level), otherwise false (edge cases: true for pickup skills if no skill has been assigned to it; true for teleporters and receivers if no object of the other type exists in the level)
"DISARMED" - Always false
"LEFT" - True for a window or splitter (currently) facing left, otherwise false
"RIGHT" - True for a window or splitter (currently) facing right, otherwise false
Any other value, including blank - Always true

(Futureproofing: "EXHAUSTED", always false. This is for the limited-count exits / entrances feature, and in-game would be "true" when their count has been used up.)
« Last Edit: May 15, 2019, 03:45:00 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Nepster

  • Posts: 1829
    • View Profile
Quote
1) Why do we have to distinguish between primary animations and secondary ones? It seems that the secondary animations are even more flexible, and the only advantage of primary animations is, that they override certain attributes. But then the style designer should not set the attributes in the first place.

Primary animation is tied to physics, same way that the lone animation up until now has been. Aside from that, they are indeed identical (outside of NL rejecting a few specific settings on the primary) - the primary animation is loaded by the same code as the secondaries just with a few properties being overridden. Rendering also uses the same code as primaries, though frame update does not - the primary is perpetually in a "paused" state, with the frame number being directly manipulated by game physics.
Sorry, but if they are so similar, then why do we need to differentiate them? I still see no reason for this. Wouldn't it be easier for both player and editor to have just one $ANIMATION keyword? Then all the issues with the Z_INDEX would disappear as well.

Quote
4) Why do we need "HIDE" as an option for secondary animations? If we are not displaying anything, then why have this secondary animation at all?
Reason 1 - future animations that don't get used directly. For example, digits for a limited-count exit / entrance.
Reason 2 - animations that don't display in default state, but do when one of the triggers is fulfilled. For an example of this, see namida_horror:exit_02. Or the inverse - displaying usually, and hiding when a condition is fulfilled - which namida_horror:exit_02 also provides an example of.
Future animations should not be part of the style (or at least not be referenced in the nxmo file). I don't understand your reason 2: Is it not possible to describe within the $TRIGGER part, that the animation should only start under some condition (potentially in combination with an empty first frame)?

Quote
6) I am still unsure which of the STATEs and the TRIGGER types are actually necessary for the most common use-cases (i.e. idling traps and locked exits), and which are just for potential future use? Can we stick to the absolute essentials for now? This would help people like me to get used to this.
I've already found use cases for all of these (except, IIRC, "TRIGGERED", but there are differences player-side between this and "BUSY"*), though some are definitely more common. But as a few examples:
- The common blinking-light makes use of the instant-stop (vs loop_to_zero), and the "BUSY" condition (light stops blinking while trap is killing) and "DISABLED" condition (light stops blinking when trap disarmed).
- The "bob up and down / side to side when idle" effect, eg. ohno_snow:trap, requires hiding secondary animations on a BUSY (or TRIGGERED) condition to avoid really weird visuals when triggered, and the "PAUSE" (without going back to frame zero) so the position won't suddenly jump by a couple of pixels (depending on current frame) when the trap gets disarmed.
- For an example of why the absence of these can be important, see ohno_brick:trap_02. The secondary animation on that one only pauses (remaining on its current frame) when the trap is disarmed, not in any other case.
Well, yes, one can always come up with some use-cases for anything. But a lot of your examples above don't sound like they occur often. As such I really don't know whether it is useful to have them: They add complexity and are potential sources for bugs. Is usage in one or two pieces really enough to compensate for this? My idea here is to implement only the most common features at first and then see what further requests are supported by multiple users here. After all - if I learned one thing from the change to new-formats, it is: Adding new features is far easier than removing existing ones.
As such I really approve your most recent changes. But I would go even farther:
- Remove gatcDisarmed and use gatcDisabled instead: The only difference is for one-use traps, but as the game basically treats used-up such traps in the same way as disarmed ones anyway (for all other purposes like display in clear-physics), I don't see a compelling reason to have this distinction here.
- Remove gatcLeft and gatcRight: For hatches, left-facing only occurs when flipping the hatch, which (hopefully) flips the secondary animations as well. So the only use-case would be splitters with a direction-dependant animation. But then I really wonder whether we need to introduce two keywords just for this use-case? I would prefer omitting them for the first version with secondary animations and see whether there is actual demand for it.

A post earlier in the topic, which I've mostly been keeping updated with new exp's, details in full how this new system works.
Ok, will probably have to read through all of this. Thanks for linking it.

Quote
The main reason is player expectations: If something triggers, then this signals a game physics effect on a lemming.
Counter-examples: Unlock buttons, pickup skills, locked exits. None of their animations directly affect a lemming physics-wise. Counter-counter-point: ...although they do all indicate something physics-related at least.
Yeah, perhaps a more precise way of stating my point would have been: If something animates, this signals a game physics effect either on a single level or the level state as a whole.

Quote
With the various gadget types it is hard enough to newer players to learn the various effects NL supports. No need to confuse them with no-effect effects ;). This all stems directly from my own experience as a player: Such objects have been very rare, so when I first encountered it, I wasn't even aware that NeoLemmix supported triggered animations. So I tried for half an hour to find out, what this gadget actually does and never found anything! :devil::devil::devil:
We have clear physics mode nowdays; and an up-to-date NeoLemmix Introduction Pack should help here too. I did start working on something, but then got bored with it for now.
But I don't want to play Lemmings in the future only in clear physics mode! My goal is still that the usual level image gives the player all the necessary information, with clear-physics being used only as a fallback.

NeoLemmix has always tried to account for both the puzzle side and the artistic side of creativity;
... but it's still mainly a computer game, not some art show. As such NeoLemmix has to strike a balance between these two, and triggered animations are far more confusing for game-play compared to the artistic advantages. At least their removal was one of the least controversial cullings, with only one or two triggered animations ever made for the old-formats styles, which tells me that there is no real need for them even from an artistic point of view.

Perhaps we should approach this as a joint effort - you know how you like things to be loaded, etc; while I understand in full how the system works, down to the technicalities / etc.
Basically I am currently using the editor support also as a check: Is this feature simple enough that other users, except you yourself, can understand and use this feature? So I would like to continue trying to implement the frame-finding in the editor, but you are more than welcome to check the implementation, once I consider my implementation finished. Nevertheless thanks for the offer! :thumbsup:
And with your latest changes we go a good way in the direction towards "simple enough for me to understand".

Quote from: Nepster
If something moves, it either is currently interacting with a lemming or has at least the potential for it.
I have pushed for more than 2 animations per gadget because of an even stronger variant of this guideline: What moves, can interact, and what does not move, is terrain.
Yeah, and I totally agree with you that we need idling animations both from an artistic and from a game-play point of view. I am just criticizing the (remaining) complexity of the current solution.



Finally one note regarding nine-slicing: I tried today to implement native editor-support for it and it worked pretty smoothly. For this feature I am happy to say: It is so simple, that even I can understand it! :thumbsup: I just have one minor suggestion: Rename the keywords from "CUT_LEFT" to "NINE_SLICE_LEFT" (and similar for the other directions). The current naming suggests (at least to me) some kind of cropping, i.e. we cut away some part of the piece.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
Future animations should not be part of the style (or at least not be referenced in the nxmo file). I don't understand your reason 2: Is it not possible to describe within the $TRIGGER part, that the animation should only start under some condition (potentially in combination with an empty first frame)?

I mean "future animations" in the sense of, features in the future that may make use of an animation, but don't want to display it the normal way. Not as in "an animation someone's creating but isn't using yet".
For #2, animations work by first defining a default STATE and (Visible / Invisible). Then, triggers can override this with a different one. For the case you describe - an animation that's visible by default, but only animates when a condition is fulfilled; you'd want the base state to be STOP or PAUSE (depending on exactly what you're trying to achieve), and then a trigger that changes this to PLAY (or possibly, LOOP_TO_ZERO) when a certain condition is fulfilled.

Quote
- Remove gatcDisarmed and use gatcDisabled instead: The only difference is for one-use traps, but as the game basically treats used-up such traps in the same way as disarmed ones anyway (for all other purposes like display in clear-physics), I don't see a compelling reason to have this distinction here.

These are different states visually, even in the current version. The most noticable difference: A single-use trap's graphic, after being used-up, may show a dead lemming. This should never happen, of course, for one that was disarmed. Even when no dead lemming is visible, the appearance of the trap in the two cases is generally radically different - if it goes back to close to its original appearance, this is quite misleading for a single-use trap - and thus I don't feel we can assume the same animation would be fit for both.

However - if the limited-count exits / entrances are going to be used, then there's another option here: We remove gatcDisarmed. gatcDisabled remains valid for either "disarmed" or "used up". Then, gatcExhausted - which currently only exists in the limited-count entrances / exits branch - could also cover single-use traps that have been used up. (Counter-argument: In this case, shouldn't they apply to other one-shot things, like buttons and pickups skills? Counter-counter-argument: Sure, that's easy enough to implement player-side, while the editor side can generally disregard it.)

PS: Any reason you reimplemented nineslicing? If you just didn't see I had implemented it, no worries. But if you did, but chose to use your own implementation - mind if I ask what in particular you weren't keen on from mine, just so I get an idea of how you want things done for future commits?
« Last Edit: May 16, 2019, 12:16:58 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Let's have an up-to-date summary of how things work now, in the latest commits in my repo (which are much newer than the latest experimental posted here) (EDIT: This is no longer true; I updated the experimental). For this, I will be describing the features in full, not just "what the editor needs to know".

Anything struck-out here is valid in the experimental, but has since been removed in the source code, so don't use it (or if you have a really good use case for it, explain that, so we can see why we should re-add it).

Basic setup
An object must have a primary animation, defined either in the base section of the object (I encourage doing it this way where possible, for backwards-compatibility) (this method will be considered deprecated and exists for backwards-compatibility purposes) or in a $PRIMARY_ANIMATION section. I will explain how these work later in the post. The primary animation is given special handling to allow defining it in the main section, which means old pieces are forwards-compatible and new pieces can be made backwards-compatible.

An object may (but does not have to) have one or more secondary animations. These are defined in $ANIMATION sections.

The primary animation's frame updating is not controlled by anything new. Just like the sole animation was in stable-NL, the primary animation is directly controlled by game physics, even when it's as simple as "a looping animation that never does anything else". This is to minimize the risk of breaking anything physics-wise. (Code exists, for future use, to allow an object's primary animation to be replaced at runtime with one of the secondaries; but this is currently not used by anything. This should not be something the user can do arbitrarily, but rather, if a future object type needs this functionality, it exists.)

Secondary animations are controlled entirely by the new animation system, which I will explain the workings of after the explanation of the data.

Defining an animation
A $PRIMARY_ANIMATION or $ANIMATION segment can contain the following data. Most of these are valid for either, but some are only valid for secondary animations.

NAME - This defines the suffix of the filename to load the animation from. For example, if the name is "example", on an object called "bob" (ie: the NXMO file is "bob.nxmo"), the animation would be loaded from "bob_example.png". If NAME is blank or absent, no suffix is applied - it would be loaded from "bob.png" (not "bob_.png"). This works the same way, regardless of whether the animation is primary or secondary - the primary can use a suffix, a secondary can use the non-suffixed file!
FRAMES - Frame count of the animation. If absent, this is treated as 1.
COLOR - Defines the color name (from the currently-active theme) to recolor the animation with. Works exactly the same way as, in the old system, a mask with a target of *SELF sould have. If absent (which it will be in 99% of cases - exceptions being the default style one-ways and pickups), no recoloring occurs.
HORIZONTAL_STRIP - Only the presence / absence (not any value) of this keyword is tested. If it's present, the frames are loaded from the PNG file horizontally rather than vertically.
Z_INDEX - Defines the order in which animations are rendered. If absent, it defaults to 1 for the primary animation, and 0 for any other animation.
INITIAL_FRAME - Sets the initial frame of the animation. This is ignored on the primary animation if the object is of a type where the frame is physics-significant (but not if it isn't). If absent, defaults to 0. "RANDOM" can be used as the value to specify a random initial frame.
OFFSET_X and OFFSET_Y - Sets the position, relative to the object's in-data-file coordinates, that the animation is rendered at. Default to zero. To be clear: These are valid for the primary animation!
NINE_SLICE_LEFT, NINE_SLICE_TOP, NINE_SLICE_RIGHT and NINE_SLICE_BOTTOM - Sets the number of pixels on each side to treat as the edge when nine-slicing.
HIDE - Only the presence / absence (not any value) of this keyword is tested for. If it's present, the default visibility of the animation is "hidden". Ignored on the primary animation.
STATE - Sets the default animation state of the animation. Ignored on the primary animation. This can be one of: "PLAY", "PAUSE", "STOP", "LOOP_TO_ZERO", "MATCH_PRIMARY_FRAME". If absent (or invalid), the default value is either "STOP" (if "HIDE" is present - but I am very strongly considering changing this to "PAUSE") "PAUSE" (if "HIDE" is present) or "PLAY" (if "HIDE" is absent).
$TRIGGER - Explained below.

Defining the primary animation in the main body of the NXMO file
Internally, NeoLemmix - if it detects that no $PRIMARY_ANIMATION section exists - it creates one based on these lines in the main segment, then parses it the same way it usually would. Any line that doesn't get created by this translation, just uses its default value. Note that not all features supported in a $PRIMARY_ANIMATION segment, are supported here. Any I list here with no comments, just get copied verbatim to the generated $PRIMARY_ANIMATION segment.
FRAMES
HORIZONTAL_STRIP
NINE_SLICE_LEFT, NINE_SLICE_TOP, NINE_SLICE_RIGHT and NINE_SLICE_BOTTOM
INITIAL_FRAME
PREVIEW_FRAME - Copied to the generated segment, with the keyword changed to "INITIAL_FRAME".
RANDOM_START_FRAME - If this is present, "INITIAL_FRAME -1" is added to the generated segment.


Defining a trigger condition on an animation
Any animation except the primary can contain any number of trigger conditions. These are order-sensitive - the later in the file, the higher the priority of it. Trigger conditions can contain the following lines:

CONDITION - The condition on which the trigger is applied. Valid values are "READY", "BUSY", "DISABLED", "DISARMED", "LEFT", "RIGHT", (in limited-exits/entrances branch only) "EXHAUSTED". Absent or invalid gets treated as an unconditional trigger - ie: it will always pass.
HIDE and STATE - These work the same way as in the base $ANIMATION segment.



Processing / displaying animations

There are three steps in updating and displaying the object, in this order - testing the trigger conditions, updating the frame number, and actually rendering the animation. The first two happen one after another during the game's physics update loop. The third one happens, of course, during object rendering. During each one of these steps, each animation is handled one-by-one.

Special case: The primary animation does not get any trigger tests or frame number updates applied (the frame number is updated by game physics). However, the actual rendering works exactly the same way as any other animation.

Testing trigger conditions

The trigger condition test starts at the last trigger in the file, and works backwards. When it finds a trigger where the condition is fulfilled, it sets the animation's current STATE and VISIBLE to whatever is defined in that trigger - and then stops testing. Should it get through all the triggers without any of the conditions being fulfilled (for example, an object with "BUSY" and "DISABLED" conditions, but it's currently idle), the animation's current STATE and VISIBLE are set to whatever is defined in the base $ANIMATION segment.

Here's a reminder on how the conditions work. This is mostly a copy-paste from the above post.

Spoiler (click to show/hide)

How the animation's frame is updated

Frame update depends on the current STATE (as set by the trigger test):

PLAY - The animation will advance by one frame every update cycle. If it reaches the end of the animation, it loops back to the beginning, and continues from there.
PAUSE - The animation remains on the current frame.
STOP - The animation jumps to frame 0. After this, STATE is changed to "PAUSED".
LOOP_TO_ZERO - This works the same way as "PLAY", except that when the animation reaches frame 0, STATE is changed to "PAUSED".
MATCH_PRIMARY_FRAME - The frame number will be set to whatever the primary animation's current frame number is. What will happen if the primary animation has more frames than this animatino, is undefined!


How the animations are rendered

The order in which animations are rendered, is determined firstly by their Z_INDEX value (lower number = rendered first), and where these are equal, by their order in the file. The primary animation is always treated as coming first in the file (regardless of where it actually is in the order), but its placement in the rendering can still be altered via Z_INDEX.

If an object is resizable, all animations are resized. However, each animation can have independent nine-slicing values. (Some interesting effects can probably be created here, by having one of the edge sizes, equal to the overall size of the animation graphic - then you can create an animation that only appears on one corner, for example. Though I need to test this, to ensure there's no infinite-loops or divide-by-zero errors that result from trying this...)

An animation will be displayed if either:
a) Its visibility is TRUE
or b) Its visibility is FALSE, and its current state isn't "PAUSE" (remember: its current state will NEVER be "STOP" at this point, due to the actions during frame number updating). Why is it this way? Because of the useful combination of visibility=false and state=loop_to_zero.



I think that covers everything. In terms of what could maybe be changed:

"LEFT", "RIGHT": Maybe these are unnessecary. Let's remove them for now, maybe? Adding them back later would be simple enough.
"DISARMED" vs "DISABLED": As mentioned above, I do feel it's important to have some way to distinguish between "disarmed" and "used up" on a single-use trap, which is why I separated these in the first place. But I'm open to acheiving this a different way - as long as distinguishing remains possible.

This does leave a lot open to designers, but (a) I notice that more and more content designers are on board with the idea of "don't try to be deceptive", than ever. These days, deceptive content mostly seems to arise from the occasional creator outside our community, or as technical demonstrations / April Fools pranks, unlike in the past where some creators went out of their way to be deceptive; and (b) I feel the flexibility this allows for in object graphical design is worth it.

I am open to perhaps having a few presets that can be set as one liners, eg "PRESET TRAP_ANIMATE_UNITL_DISABLED", "PRESET TRAP_ANIMATE_IDLE_ONLY", etc, that will automatically set STATE, HIDE and $TRIGGERs as needed for some of the most common use cases. Then, only people who want to produce less-common effects need to worry about the full intracicies of the system.

Regarding less differentiation for the primary animation and getting rid of Z_INDEX (the former of which I feel is necessary if we're going to do the latter) - yeah, we could make this an $ANIMATION section that includes the "PRIMARY" keyword. The only thing here is we need a rule here - if the primary is instead defined in the main section (and I absolutely want to keep this, for many reasons), does it get treated as being first or last in the animation list? I would lean towards "last" here, for the same reason I defaulted it's Z-index to 1 - although not always, we'll usually want to draw the secondary behind the primary in case of overlap.

And to clarify another point I just spotted:

Quote
For hatches, left-facing only occurs when flipping the hatch, which (hopefully) flips the secondary animations as well.

Yes - to confirm, secondary anims get flipped / inverted / rotated along with the main one, and yes, their offsets also get moved accordingly. To the user, it would be indistinguishable from rotating / flipping / inverting a single large composite image (even though internally, each animation is reoriented and positioned separately).
« Last Edit: May 19, 2019, 10:19:20 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Nepster

  • Posts: 1829
    • View Profile
Thanks for the concise summary of the current state :thumbsup::thumbsup:

Quote
Regarding less differentiation for the primary animation and getting rid of Z_INDEX (the former of which I feel is necessary if we're going to do the latter) - yeah, we could make this an $ANIMATION section that includes the "PRIMARY" keyword. The only thing here is we need a rule here - if the primary is instead defined in the main section (and I absolutely want to keep this, for many reasons), does it get treated as being first or last in the animation list? I would lean towards "last" here, for the same reason I defaulted it's Z-index to 1 - although not always, we'll usually want to draw the secondary behind the primary in case of overlap.
Sorry for being obnoxious here, but I still don't understand why we need either a $PRIMARY-ANIMATION or a PRIMARY keyword? What would turn impossible if we just had the usual .png, that we already had in all previous versions, and then only the secondary animations? In other word: What would be impossible to do, if I never used either $PRIMARY-ANIMATION or the PRIMARY keyword?

Quote
"LEFT", "RIGHT": Maybe these are unnessecary. Let's remove them for now, maybe? Adding them back later would be simple enough.
"DISARMED" vs "DISABLED": As mentioned above, I do feel it's important to have some way to distinguish between "disarmed" and "used up" on a single-use trap, which is why I separated these in the first place. But I'm open to acheiving this a different way - as long as distinguishing remains possible.
You convinced me, that having "DISARMED" as an extra keyword is worth having. But I would still remove the "LEFT" and "RIGHT" keywords for now (though keep the code in an extra branch, so that we can easily merge it back into the main one if desired).

Quote
PS: Any reason you reimplemented nineslicing? If you just didn't see I had implemented it, no worries. But if you did, but chose to use your own implementation - mind if I ask what in particular you weren't keen on from mine, just so I get an idea of how you want things done for future commits?
Yeah, I simply didn't see your implementation, mainly because I didn't think of checking and was on a train ride when I coded my implementation. See your PMs for a code-review of your implementation.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
Sorry for being obnoxious here, but I still don't understand why we need either a $PRIMARY-ANIMATION or a PRIMARY keyword? What would turn impossible if we just had the usual .png, that we already had in all previous versions, and then only the secondary animations? In other word: What would be impossible to do, if I never used either $PRIMARY-ANIMATION or the PRIMARY keyword?

I'm not quite getting what part of this doesn't make sense, so I'll try to cover everything. Also because this way, I'm thinking through it again myself.

The primary animation, in this system, is equivalent to the only animation under the old system. As such - it is tied to physics. Physics control which frame it's on, and which frame it's on can affect physics.
Could this be changed, so that the primary animation is just like any other? It's not impossible. But let's take for example, a DOM_TRAP. The frame count of the primary animation affects physics - if it has 20 frames, it can't kill another lemming for 20 physics updates. But if it has 10, it can kill another lemming 10 physics updates later. Also, the primary animation remains stopped until the trap is triggered, at which point it animates once. We do have "LOOP_TO_ZERO", but this will do nothing if set while the animation is already on frame zero; its purpose is to allow the animation to complete gracefully (then not repeat), rather than abruptly terminate, when we want the animation to stop. Thus, some kind of "RUN_ONCE" state (which presumably, advances the frame to 1 then changes to LOOP_TO_ZERO) - which would also need extra code to differentiate between eg. "the trap is still triggered" vs "the trap has been triggered again" - would have to be introduced. The object would still need to define how many physics updates the object is triggered for anyway. To me, it sounds like this would just complicate things.

The primary animation can be defined in either a $PRIMARY_ANIMATION segment (or as an alternate proposal: an $ANIMATION segment with a "PRIMARY" keyword), or in the main segment of the NXMO file.
Could this be changed, to only one or the other? Changing it to the sub-segment only would require modifying all existing NXMO files, and break forwards / backwards compatibility completely (by comparison, under the current experimental, new objects using the main segment are backwards compatible physics-wise though might look weird, while old objects are 100% forwards-compatible). On the other hand, changing it to the main segment only - that's feasible, yeah. Maybe that makes more sense than having the A or B options. The downside here is - either loading code needs to be duplicated, or the main segment has to be parsed a second time (as an $ANIMATION segment), or it just ends up getting translated internally to an $ANIMATION segment with special handling (which is pretty much what already happens). Also, I don't really know how I'd feel about having a "NAME" parameter for an animation, in the main segment - though perhaps NAME could just be disallowed altogether for the primary.

Or is it, the primary animation shouldn't need to be explicitly defined?
For this, I'd assume the rule is that either the first or last $ANIMATION, becomes the primary one. If so - this works only if we keep Z_INDEX, otherwise it's not possible to choose whether secondaries are drawn above or below the primary. This is a feasible - if maybe a bit counter-intuitive - approach if Z_INDEX is kept.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Nepster

  • Posts: 1829
    • View Profile
Quote
The primary animation can be defined in either a $PRIMARY_ANIMATION segment (or as an alternate proposal: an $ANIMATION segment with a "PRIMARY" keyword), or in the main segment of the NXMO file.
Yeah, that's a much better way to ask my question, than what I wrote above. Until your latest post, I was confused when to use $PRIMARY_ANIMATION and when to use the main section (or whether I have to duplicate stuff now).

Quote
The downside here is - either loading code needs to be duplicated, or the main segment has to be parsed a second time (as an $ANIMATION segment), or it just ends up getting translated internally to an $ANIMATION segment with special handling (which is pretty much what already happens).
If adding a $PRIMARY-ANIMATION part is still optional, then you will have to parse it (twice?) regardless of whether we change it to main-section only or not. So I don't see any downside of forcing primary animations to be defined in the main section. If nothing else, then it removes the confusion about when to define stuff in the main section and when to use $PRIMARY-ANIMATIONs.

Quote
Also, I don't really know how I'd feel about having a "NAME" parameter for an animation, in the main segment - though perhaps NAME could just be disallowed altogether for the primary.
Good point, but so far it worked pretty well to use the same name for the .nxmo file and the main animation .png file.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
If adding a $PRIMARY-ANIMATION part is still optional, then you will have to parse it (twice?) regardless of whether we change it to main-section only or not. So I don't see any downside of forcing primary animations to be defined in the main section. If nothing else, then it removes the confusion about when to define stuff in the main section and when to use $PRIMARY-ANIMATIONs.

The intent here - $PRIMARY_ANIMATION would eventually become the "normal" way of defining it, and - a long way down the track - use of the main section would be deprecated and maybe eventually removed. However, for now, use of the main section was preserved for backwards (and to some extent, forwards) compatibility. This is why I didn't bother to code full support for all of the new options, but instead just popped in code that translates in-memory the main section lines to a $PRIMARY_ANIMATION segment.

Had this been a new engine being made from scratch, with no worries of existing content or what existing users are familiar with, it would be only $PRIMARY_ANIMATION (or $ANIMATION with PRIMARY keyword - one or the other, not "either one is valid") for sure.

Quote
If adding a $PRIMARY-ANIMATION part is still optional, then you will have to parse it (twice?) regardless of whether we change it to main-section only or not. So I don't see any downside of forcing primary animations to be defined in the main section. If nothing else, then it removes the confusion about when to define stuff in the main section and when to use $PRIMARY-ANIMATIONs.

Thinking a bit more clearly now - if the primary is defined in the main segment, there'll be some duplication either way, unless we literally just parse the main section twice - once as a main NXMO section, and once as an $ANIMATION section would be, keeping in mind this also means we need to make sure there's never keyword overlap between the two. At best, this would consist of the same translation code currently used to turn this into a $PRIMARY_ANIMATION segment (which can be loaded as a normal animation, just with a few things overridden / ignored), just without the option of using such a segment directly - and in turn, without the option of ever deprecating it.

This way, adds a small bit of extra work now, in exchange for - once this feature's stable and been around a good amount of time - being able to deprecate the main-segment way further down the track more cleanly.

Quote
Good point, but so far it worked pretty well to use the same name for the .nxmo file and the main animation .png file.

Yeah, this is probably the simplest way if we go with main-section-only - just add NAME to the fields prohibited on the primary. I would like to keep the "if no name is specified, the main PNG file is used" capability on secondaries though, as two secondaries with the same graphic (and thus, a primary and a secondary with the same graphic) is allowed, and at any rate prohibiting this would be trivial to bypass (by creating a second copy of the PNG file with a different name). Of course this shouldn't be used on things like exits, traps, etc, but it might be useful for decorative objects.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
As per discussions me and Nepster have had in PM, the keywords "CUT_LEFT" etc are changed to "NINE_SLICE_LEFT" etc. They still work exactly the same; just the keyword itself is changed.

As per the discussions in this topic, the "LEFT" and "RIGHT" conditions have (for now) been removed. If a use case where these are needed arises, please bring it up, and we can consider re-adding them.
« Last Edit: May 17, 2019, 10:35:39 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Nepster

  • Posts: 1829
    • View Profile
The intent here - $PRIMARY_ANIMATION would eventually become the "normal" way of defining it, and - a long way down the track - use of the main section would be deprecated and maybe eventually removed.
Ok, now I can understand why you introduced the $PRIMARY_ANIMATION. While I wouldn't have done so myself, it might actually prove useful in the long run. So let's keep the $PRIMARY_ANIMATION (which I prefer over adding a PRIMARY attribute to secondary animations), but do three other things as well:
1) Deprecate the old way right away (which is mainly a case of making this change in the graphics tool - but does anyone still use it?)
2) Clearly communicate that new styles should use $PRIMARY_ANIMATIONs. Don't we have some guidelines about graphic style creation here on the forums, where this should be added?
3) Fully change a few styles right now, so that other graphic designers have enough examples to copy from.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
Ok, now I can understand why you introduced the $PRIMARY_ANIMATION. While I wouldn't have done so myself, it might actually prove useful in the long run. So let's keep the $PRIMARY_ANIMATION (which I prefer over adding a PRIMARY attribute to secondary animations), but do three other things as well:
1) Deprecate the old way right away (which is mainly a case of making this change in the graphics tool - but does anyone still use it?)
2) Clearly communicate that new styles should use $PRIMARY_ANIMATIONs. Don't we have some guidelines about graphic style creation here on the forums, where this should be added?
3) Fully change a few styles right now, so that other graphic designers have enough examples to copy from.

1. Alright. Let's have a solid plan for when it will be removed altogether - maybe something like "whichever comes last, 2 further major version updates, or the first major version update after 6 months from now". Not sure re: use of graphic set tool, I know the majority of users don't use it but I don't know if it goes as far as "no one does". Latest version has 431 downloads, but (a) this is over a whole year, (b) a lot of that likely comes from webcrawlers, (c) if the installer downloads it via a neolemmix.com/download.php link, that will be included in these counts, and (d) downloaded != uses.
2. Yep, this makes sense. We should have a "Information regarding V12.05.00 for style creators" topic, separate from the release topic, and stickied. As well as drawing attention to this change, it can also explain the new features, including best practices for using them.
3. I'm a bit reluctant to change anything we don't need to right away. It would be preferable that - at least for a little while - the styles download remains physics-compatible with older versions, as well as as graphically compatible as can be achieved without either (a) being detrimental to the new version in any way, or (b) using ugly hacks, like having both info in main segment and a $PRIMARY_ANIMATION. Perhaps wait until a V12.5.1 update to do this.

In regards to actually converting styles, it should be possible to make a command line tool that automates this. As explained in a post earlier in the topic, NeoLemmix handles data in the main segment of the file, by internally translating it to a $PRIMARY_ANIMATION segment - this isn't a metaphor, it's literally what NeoLemmix does. Doing this, removing the old lines, and writing the result to a file, would be a sufficient tool. The only case it wouldn't cover is objects that use the MASK feature, and as far as I know, this was only ever default:pickup and default:owa_XXXX - easy enough to handle these two manually, plus the mask side of things is already updated in the repo.
« Last Edit: May 19, 2019, 08:56:21 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
As per Nepster's suggestion earlier in the topic (or maybe in PM?), "INITIAL_FRAME RANDOM" is now valid and recognized input. A negative number will also have the effect of a random initial frame, but let's encourage use of "INITIAL_FRAME RANDOM" rather than "INITIAL_FRAME -1".

Additionally, I have removed the support for recognizing the older "PREVIEW_FRAME" and "RANDOM_START_FRAME". Why? Because (unless they're used by some very recent - as in last few weeks - addition to the styles download) the former hasn't been used at all, and the latter is only used in a handful of my own styles - which I can easily make sure are up to date, and already have done so in the repo. Very possibly because the style convertor didn't handle them properly, and their existance was never really drawn attention to.

The earlier post detailing the system has been updated to reflect:
a) $PRIMARY_ANIMATION should be used in all cases, and define-in-main-segment should be treated as deprecated (previously, it was suggested that for now $PRIMARY_ANIMATION should only be used if use of an unsupported-in-main-segment new feature was desired)
b) Change of "CUT_xxxx" to "NINE_SLICE_xxxx"
c) Removal of "PREVIEW_FRAME" and "RANDOM_START_FRAME" in the backwards-compatibility code
d) Removal of the "LEFT" and "RIGHT" conditions
e) Addition of "INITIAL_FRAME RANDOM" as recognized (previously, no special keyword, but a negative number would have the same effect - negative number still works, because it'd be extra effort to make it not work, but advise against using it as "RANDOM" is clearer)
f) "HIDE" specified without a corresponding STATE, will set the state to PAUSE (previously STOP)

In terms of what the editor needs to know to account for these:
a) Nothing, for now. The editor still needs to, for now, be able to understand either format.
b) Keyword change
c) The editor can remove the code (if any) for handling these two keywords
d) The editor can remove the code (if any) for handling these two conditions
e) The editor should recognize "INITIAL_FRAME RANDOM" and treat it as equivalent to a negative INITIAL_FRAME, however the editor handles those (player-side, a random frame is chosen, but editor side a rule like "always frame 0" might make more sense)
f) Nothing. HIDE + PAUSE and HIDE + STOP are equivalent as far as the editor needs to account for (there are subtle differences player-side - when the animation becomes visible again, it'll be on frame 0 in the case of HIDE + STOP, but on whatever frame it was on before disappearing (or the initial frame, if it's never animated or been STOP'd yet) in the case of HIDE + PAUSE).
« Last Edit: May 19, 2019, 10:29:23 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Simon

  • Administrator
  • Posts: 3851
    • View Profile
    • Lix
If DISABLED is one-shot trap that has been used, then rename to EXHAUSTED. It's the same principle as behind the exit that closes after a given number of exiters.

Will read more tomorrow.

-- Simon

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Quote
If DISABLED is one-shot trap that has been used, then rename to EXHAUSTED. It's the same principle as behind the exit that closes after a given number of exiters.

I proposed this earlier: Remove "DISARMED" (which I think is what you're thinking of here - "DISABLED" has a much larger scope, though does include such a situation), and expand the definition of "EXHAUSTED" to include such traps instead. As far as I can tell, with no changes to "DISABLED" (ie: a single-use trap fulfills this condition when either used-up or disarmed), anything possible with the "DISARMED" keyword (fulfilled by a single use trap when disarmed, but not when used-up) is also possible with the proposed expansion of the "EXHAUSTED" keyword (fulfilled by a single use trap when used-up, but not when disarmed) - any such setup using the current keywords (which can be changed without BC concerns, as they don't yet exist outside of experimentals and experimental features are never guaranteed), reverse the order of the DISABLED and DISARMED conditions, then replace DISABLED with EXHAUSTED, and DISARMED with DISABLED.

Some changes to be aware of on other object types:
- "DISABLED" and "DISARMED" are currently identical for infinite-use traps. However, I feel "EXHAUSTED" should never be true for these (thus, disarmed infinite-use traps can only be detected via the DISABLED condition).
- I feel it would make sense for "EXHAUSTED" to be true on a pickup skill that's been used. I have no particularly strong preference either way as to whether "DISABLED" also remains true for such; it's currently that way because no other rule allows detecting a used-up pickup skill, and DISABLED's general rule fits better than any other rule does. (Although maybe based on the "general rule" argument, DISABLED should remain true as well for pickup skills that are used up.)
- Same logic as pickup skills, also goes for unlock buttons.

The changes described in this post are not yet implemented; just proposals.
« Last Edit: May 19, 2019, 11:45:10 PM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline Nepster

  • Posts: 1829
    • View Profile
Quote
DISABLED vs. DISARMED vs. EXHAUSED
To make namida's suggestion somewhat easier to digest, I tried to create a table for the current implementation.
- "--" means that this situation can never happen
- "disarmed" means that a lemming with a disarmer skill worked on it
- "used" means that (one or more) lemmings interacted with the object normally and due to that it can no longer interact
Code: [Select]
   Type    | DISABLED | DISARMED         | EXHAUSTED
-----------+----------+------------------+-----------
Inf. Trap  | disarmed | disarmed         |  --
Once Trap  | disarmed | disarmed or used | used
Pickup     | used     |  --              | used
Button     | used     |  --              | used
Limit Exit |  --      |  --              | used
The suggestion seems to be something like:
Code: [Select]
   Type    | DISABLED | EXHAUSTED
-----------+----------+-----------
Inf. Trap  | disarmed |  --
Once Trap  | disarmed | used
Pickup     |  --      | used
Button     |  --      | used
Limit Exit |  --      | used
Please correct me (or just edit this post), if I made mistakes in summarizing your post. I am in favor for the change (i.e. having only DISABLED and EXHAUSED).

2. Yep, this makes sense. We should have a "Information regarding V12.05.00 for style creators" topic, [...] Perhaps wait until a V12.5.1 update to do this.
The shimmier together with the secondary animations seem large enough to me to go even to V13.0.0. While this might not be totally according to semantic versioning, this might help players to distinguish versions and ensure compatibility between player versions and level packs.

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Your "current" table is correct on everything except one - "EXHAUSTED" would currently be "--" for everything except limited entrances / exits. (Exhausted exists only in the limited entrance / exit branch, and is currently only applicable to them.)

For the proposed, DISABLED would not be changed in any way. Your thoughts on "EXHAUSTED" are spot-on.

However - I am now thinking (and this is thinking ahead to the limited exits / entrances again), based on how "DISABLED" is applied in general, it should be true for limited entrances/exits when used up, too. For limited entrances and limited non-lockable exits, the two would thus become identical. For locked exits, there'd be a difference: "DISABLED", but not "EXHAUSTED", is true before the entrance has been unlocked.

See my earlier table (post #42, about half way through the post, behind a spoiler tag) for the general rules I've been applying here, keeping in mind (a) the rules themself can be changed if there's good reason, and (b) there are definitely cases where it's questionable if I applied it consistently, so if you disagree with how I've applied it (and it isn't obviously a case of "this is a situation we need to distinguish, but it's close enough to this, that we don't need an additional condition altogether"), please mention this.

I consider it acceptable for two conditions to have near or full overlap for a certain object type, as long as this is consistent with the general rules of the conditions.

Quote
The shimmier together with the secondary animations seem large enough to me to go even to V13.0.0. While this might not be totally according to semantic versioning, this might help players to distinguish versions and ensure compatibility between player versions and level packs.

Are we going to increase the major version number every time we eg. introduce a new skill? Or is it mostly the secondary anims you feel this way about? If it's the former, I'd suggest we try to get in any other planned new skills first as well (which IIRC is just the Jumper) - and maybe a few more of the planned features as well. (In exchange, I think it's also fine for a major update to take longer than a normal update to be ready.) I also think a major version upgrade would be a perfect time to introduce the username feature - I recall you wanted more clarification around this; the best place to discuss that would be Proxima's topic about record overwriting from other user's replays, which was the reason for introducing this feature.

In this case, I suggest we look at back-porting some of the bugfixes and minor features (like mass image dumping) to make a V12.4.1. If we go with such a plan, I'm happy to work on preparing such an update. (EDIT: See the v12.4.1 branch on my repo. This should integrate all bugfixes and minor features (restored image dumping, slightly nicer alpha blending, external and directional-select cursors, solid color objects / lemmings in clear physics mode), without pulling in the Shimmier, secondary animations, or nine-slicing.)

EDIT: Okay, implemented this now.
« Last Edit: May 25, 2019, 08:36:39 AM by namida »
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
Okay, I've made some updates to this.

Spoiler (click to show/hide)
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)

Offline namida

  • Administrator
  • Posts: 12396
    • View Profile
    • NeoLemmix Website
I've now implemented defined behaviour, for cases where an image is to be drawn nine-sliced, to a target rectangle smaller than the nine-slice margins. I think how this works is easier to explain with examples. I'm only going to explain it in terms of the horizontal slicing; the vertical works exactly the same way (except with top / bottom / height as applicable, rather than left / right / width).

Example 1
Let's suppose we have an object with a left nine-slice margin of 10px, and a right nine-slice margin of 8px (18px total margin). We're trying to draw this resized to 13px wide.

This gives 5px of overlap. Half of this is removed from each side; in the case of an odd value, the remainder is taken off whichever side has the greater margin. So in this case - 3px is taken off the left margin, and 2px is taken off the right margin.

The object will be rendered, with the left 7px and the right 6px.

Example 2
Let's suppose we have an object with a left nine-slice margin of 3px and a right nine-slice margin of 12px. We want to draw this resized to 6px wide.

If we follow Example 1's formula, we end up with -1px from the left and 7px from the right. Of course, we can't really draw -1 pixels. Instead, if a margin is pushed under 0, it gets set to exactly 0, with the other side reduced to compensate. So in this case - we'd get 0px from the left, and 6px from the right.


While the extreme cases of this seem unlikely in real-world situations, I have already found a use case that benefits from more moderate usage of this behaviour.



That aside, I've also implemented an explicit test for "we're trying to resize in one direction, when the center area size in that dimension is zero". For example, if an object that's 32 pixels wide, has a left and right nine-slice margin of 16px each - there's no center area, so NeoLemmix will now raise an exception (whereas previously, it would have either crashed with a generic exception or gotten stuck in an infinite loop) if you try to draw this nine-sliced horizontally to a larger size. However - assuming it has a non-zero center area vertically, nine-slicing it vertically would still be 100% valid, as would drawing it with a smaller horizontal size (as this doesn't need a center area).


Both of the above changes are implemented in commit e697982.
My Lemmings projects
2D Lemmings: NeoLemmix (engine) | Lemmings Plus Series (level packs) | Doomsday Lemmings (level pack)
3D Lemmings: Loap (engine) | L3DEdit (level / graphics editor) | L3DUtils (replay / etc utility) | Lemmings Plus 3D (level pack)