I'm trying to create a furniture object that plays an animation when a character sits on it. I need this animation to be layered in front of the player, though, so I can't just animate the object itself. I've been trying to find a way to animate the cover image. I haven't found a way to make a .frames file work, and I have no clue how to use .animation files. Am I missing something, or can someone point me to an animation file tutorial that actually makes sense?
You need to learn lua fully - not piece meal. Or you will have a hard time writing the code properly. Once you learn it - you will be able to make sense of the animation file by looking at vanillia files such as alarm and turrets.
I've figured out how to control the state of an animation with Lua, as well as read an object's parameters. What I haven't been able to do is set the cover image. I can read it, but not set it. Is it even possible to set sitCoverImage in a Lua script?
You would likely have to remake the entire object do to it in lua. Separate the image into parts and set animation to zlevel to 3 or 4. ( I think player is 3. )
If you use cover image, that I believe is handled by the engine specially and probably can't have the flexibility you need especially the ability to use animation(possibly I'm wrong). You will want a separate "part" that can be independently manipulated. But if all you really needed was the ability to reference a different png file, that could probably be easily done with tags. Tags can replace parts of strings, can be defaulted in .animation files, and changed easily in LUA.
I set the animation to a higher zLevel, as far as I can tell, but the part I'm trying to animate is still appearing behind the player. Also, I'm trying to stop the animation when the player stands up from the object, but that's a different problem. Things are not documented very well.
This is for the bath, right? I'll give you a useful example. This is an object(modified from original) that's part of an old ship mod I'm restoring(well really done). Code: { "objectName" : "vepr_lextoilet", "rarity" : "uncommon", "description" : "Waste isn't a problem anymore! The LV1 compacts it and throws it out of a spacechute for only *299.", "shortdescription" : "vepr LEX Toilet", "race" : "generic", "category" : "furniture", "price" : 400, "colonyTags" : [ "pretty", "misc" ], "objectType" : "loungeable", "apexDescription" : "Let me guess: it's bigger on the inside?", "avianDescription" : "A place for droppings.", "floranDescription" : "Ssshiny smooth brain bowl.", "glitchDescription" : "Interest. No longer will disposal of waste material be an issue.", "humanDescription" : "Why do so many spaceships not have this!?", "hylotlDescription" : "Elegant toilet.", "inventoryIcon" : "vepr_lextoileticon.png", "orientations" : [ { "image" : "vepr_lextoilet.png:default.<frame>", "direction" : "left", "flipImages" : true, "sitPosition" : [-5, 23], "imagePosition" : [0, 0], "frames" : 12, "animationCycle" : 2.0, "spaceScan" : 0.1, "anchors" : [ "bottom" ] }, { "image" : "vepr_lextoilet.png:default.<frame>", "direction" : "right", "flipImages" : false, "sitPosition" : [14, 23], "imagePosition" : [0, 0], "frames" : 12, "animationCycle" : 2.0, "spaceScan" : 0.1, "anchors" : [ "bottom" ] } ], "sitStatusEffects" : [ "nude" ], "sitFlipDirection" : false, "scripts" : [ "autoFlush.lua" ], "scriptDelta" : 10, "animation" : "toilet.animation", "animationParts" : { "toilet" : "vepr_lextoilet.png" }, "animationCustom" : { "sounds" : { "flush" : [ "/sfx/objects/toilet_flush.ogg" ], "wait" : [ ] } }, "animationPosition" : [0, 0] } Code: { "globalTagDefaults" : { "direction" : "Right" }, "animatedParts" : { "stateTypes" : { "toilet" : { "default" : "wait", "states" : { "wait" : { "frames" : 1, "cycle" : 0.15 }, "flush" : { "frames" : 1, "cycle" : 0.15, "mode" : "transition", "transition" : "wait" } } } }, "parts" : { "toilet" : { "properties" : { "centered" : false }, "partStates" : { //"toiletState" : { "toilet" : { "wait" : { "properties" : { "image" : "<partImage>:default.<frame>" } }, "flush" : { "properties" : { "image" : "<partImage>:default.<frame>" } } } } } } }, "sounds" : { "wait" : [], "flush" : [ "/sfx/objects/toilet_flush.ogg" ] } } Code: function init() storage.id = entity.id() storage.autoFlush = false storage.cooldown = 0 end function update(dt) local occupied = world.loungeableOccupied(storage.id) if storage.autoFlush and occupied then --nothing elseif occupied then storage.autoFlush = true storage.cooldown = 1.50 elseif storage.cooldown > 0 then storage.cooldown = storage.cooldown - dt elseif storage.autoFlush then storage.autoFlush = false animator.setAnimationState("toilet", "flush", false) animator.playSound("flush", 1) end end I made that simple script because changing objects that make sounds on interaction to objects that you can "sit" on causes the sounds to no longer play, or at least it did with this toilet(which was not originally scripted). I also made sure the flush only occurs after(plus a small delay) the object is no longer in use. So that might be useful to you, but doesn't directly answer your question. But it does help because you see examples of some key functions here that you need. Before that, even if you don't know any languages, I'll tell you this. Functions often have "returns" aka after completion they can pass data back. world.loungeableOccupied() would return if the object is in use or not. The return is either true or false entity.id() returns the id of the object itself and you need to feed that info... like I did (setting storage.id) or like this occupied = world.loungeableOccupied( entity.id() ) The return of entity.id() is fed into world.loungeableOccupied() occupied is either true or false, and that is how you would know if the object is in use. Now you just need to know how to change the animation based off knowing this. animator.setAnimationState("example_partName", "example_stateName", false) or in my case(as you can see from the object and animation files) I have a part named "toilet" and (from the animation file) my part has a "partState" named "flush" that this function is switching to. That's how you'll trigger your animation. In case you're wondering about that false being sent to animator.setAnimationState() The documentation states that this "If ... true, restart the animation loop if it's already active." and I don't find that desirable behavior, but that's just me. My script there is pretty simple and you still don't need most of it Lets say in your animation file you have 2 parts "tub" and "shower" Lets say you defined 2 states for "shower" called "inUse" and "idle" Lets say that "inUse" is animated, and may even have particles too (once you learn about particles). At a minimum, all you need for a script is this Code: function init() end function update() local occupied = world.loungeableOccupied( entity.id() ) if occupied then animator.setAnimationState("shower", "inUse", false) else animator.setAnimationState("shower", "idle", false) end end But why be satisfied with just that? Play with the code. Read code from the assets and other mods, attempt to emulate and/or modify the code you find. Break things and otherwise fail. Learn from it. LUA vastly expands what is possible for your mods. It is well worth getting a handle on, even if a bit at a time. For example you could have the showerhead spawning water droplets(rather than just animated fake water), and the drain area actively draining them - either all the time, once the water level reached a certain height, and also once the object was no longer in use. edit: I should have read closer... you already figured that much out. My bad.
This tutorial mentions that renderLayer, to be above the player, should be "player+1"?, and that "zLevel doesnt directly relate to the player, only renderLayer does => The exact spot the Player occupies basically is renderLayer "player" without any adjustment or zLevel." To "stop the animation when the player stands up", I think you should put your animation-stopping thingy in a function called "uninit()", or maybe use object.isTouching(playerId) (implying you're storing the playerId when interacted) to know when the player "leaves" your object (it wouldn't be immediate tho)