Modding Discussion transform.png following player facing

Discussion in 'Starbound Modding' started by EmissaryOfInfinity, Jan 5, 2017.

  1. EmissaryOfInfinity

    EmissaryOfInfinity Subatomic Cosmonaut

    While I was working on my Metroid do-dads, I came across a rather disappointing fact: The transformation image used for the Distortion Sphere only ever faces right, and there's no way to change this with custom Distortion Spheres if you use the base distortionsphere.lua as a requirement. This can result in some rather "interesting" scenarios, like what you see below.
    Right https://gyazo.com/521d0b7927cdbc34083853b429f2ee2b
    Left https://gyazo.com/96d5f27e97c3331c9eaea81ac7447408

    Of course, me being the aesthetic perfectionist that I am, I immediately set out to remedy this. And what I found is that you can circumvent that problem rather easily, with a bit of know-how. All I had to do is stop using Distortion Spheres, and start using Morph Balls!

    1. Copy distortionsphere.lua and all related files from the base tech folder, and put them in your modded tech folder. Rename them all to whatever you want your new base sphere to be.

    2. Open each of the files, ctrl+F to replace "distortionsphere" with whatever you named your new tech.

    3. Find the section that defines "function activate()" around line 180. It should look something like this:
    Code:
    function activate()
      if not self.active then
        animator.burstParticleEmitter("activateParticles")
        animator.playSound("activate")
        animator.setAnimationState("ballState", "activate")
        self.angularVelocity = 0
        self.angle = 0
      end
      tech.setParentHidden(true)
      tech.setParentOffset({0, positionOffset()})
      tech.setToolUsageSuppressed(true)
      status.setPersistentEffects("movementAbility", {{stat = "activeMovementAbilities", amount = 1}})
      self.active = true
    end
    
    You'll need to replace the first section with something that can detect direction, and luckily Starbound makes this pretty easy with its mcontroller hooks. We make one section checking if the player is facing right (facingDirection returns a value of 1 if so), and one checking for left (facingDirection returns -1), then assign ballState values to each.
    Code:
    function activate()
      if not self.active and mcontroller.facingDirection() > 0 then
        animator.burstParticleEmitter("activateParticles")
        animator.playSound("activate")
        animator.setAnimationState("ballState", "activate_right")
        self.angularVelocity = 0
        self.angle = 0
      elseif not self.active and mcontroller.facingDirection() < 0 then
        animator.burstParticleEmitter("activateParticles")
        animator.playSound("activate")
        animator.setAnimationState("ballState", "activate_left")
        self.angularVelocity = 0
        self.angle = 0
      end
      tech.setParentHidden(true)
      tech.setParentOffset({0, positionOffset()})
      tech.setToolUsageSuppressed(true)
      status.setPersistentEffects("movementAbility", {{stat = "activeMovementAbilities", amount = 1}})
      self.active = true
    end
    
    You'll need to do the same for the "function deactivate()" section as well, which is a bit trickier thanks to the check for if the player isn't in sphere form.
    Code:
    function deactivate()
      if self.active and mcontroller.facingDirection() > 0 then
        animator.burstParticleEmitter("deactivateParticles")
        animator.playSound("deactivate")
        animator.setAnimationState("ballState", "deactivate_right")
     elseif self.active and mcontroller.facingDirection() < 0 then
        animator.burstParticleEmitter("deactivateParticles")
        animator.playSound("deactivate")
        animator.setAnimationState("ballState", "deactivate_left")
      else
        animator.setAnimationState("ballState", "off")
      end
      animator.stopAllSounds("forceDeactivate")
      animator.setGlobalTag("ballDirectives", "")
      tech.setParentHidden(false)
      tech.setParentOffset({0, 0})
      tech.setToolUsageSuppressed(false)
      status.clearPersistentEffects("movementAbility")
      self.angle = 0
      self.active = false
    end
    
    Now you'll need to make some tweaks to your .animation file. Currently, it only contains info for the Vanilla activate and deactivate states, which no longer exist for our tech thanks to the tweaks we've made here. Thankfully, this is even easier. It's really just a game of copy-paste at this point, so I'll let you spot the differences.
    Code:
    {
      "globalTagDefaults" : {
        "rotationFrame" : "0",
        "ballDirectives" : ""
      },
    
      "animatedParts" : {
        "stateTypes" : {
          "ballState" : {
            "default" : "off",
            "states" : {
              "deactivate_left" : {
                "frames" : 4,
                "cycle" : 0.1,
                "mode" : "transition",
                "transition" : "off"
              },
             "deactivate_right" : {
                "frames" : 4,
                "cycle" : 0.1,
                "mode" : "transition",
                "transition" : "off"
              },
              "off" : { },
              "activate_left" : {
                "frames" : 4,
                "cycle" : 0.1,
                "mode" : "transition",
                "transition" : "on"
              },
             "activate_right" : {
                "frames" : 4,
                "cycle" : 0.1,
                "mode" : "transition",
                "transition" : "on"
              },
              "on" : { }
            }
          }
        },
    
        "parts" : {
          "ballGlow" : {
            "properties" : {
              "transformationGroups" : [ "ball" ],
              "centered" : true
            },
            "partStates" : {
              "ballState" : {
                "on" : {
                  "properties" : {
                    "zLevel" : 1,
                    "fullbright" : true,
                    "image" : "<partImage>:<rotationFrame><ballDirectives>"
                  }
                }
              }
            }
          },
          "ball" : {
            "properties" : {
              "transformationGroups" : [ "ball" ],
              "centered" : true
            },
            "partStates" : {
              "ballState" : {
                "deactivate_right" : {
                  "properties" : {
                    "offset" : [0, 0],
                    "fullbright" : false,
                    "image" : "transform_right.png:deactivate.<frame>"
                  }
                },
                "deactivate_left" : {
                  "properties" : {
                    "offset" : [0, 0],
                    "fullbright" : false,
                    "image" : "transform_left.png:deactivate.<frame>"
                  }
                },
                "activate_right" : {
                  "properties" : {
                    "offset" : [0, 1.5],
                    "fullbright" : false,
                    "image" : "transform_right.png:activate.<frame>"
                  }
                },
                "activate_left" : {
                  "properties" : {
                    "offset" : [0, 1.5],
                    "fullbright" : false,
                    "image" : "transform_left.png:activate.<frame>"
                  }
                },
                "on" : {
                  "properties" : {
                    "image" : "<partImage>:<rotationFrame><ballDirectives>"
                  }
                }
              }
            }
          }
        }
      },
    
      "transformationGroups" : {
        "ball" : { "interpolated" : true }
      },
    
      "particleEmitters" : {
        "activateParticles" : {
          "particles" : [ ]
        },
        "deactivateParticles" : {
          "particles" : [ ]
        }
      },
    
      "sounds" : {
        "activate" : [ "/sfx/tech/morph.ogg" ],
        "deactivate" : [ "/sfx/tech/stand.ogg" ],
        "forceDeactivate" : [ "/sfx/tech/stand.ogg" ]
      }
    }
    
    Now you're free to make your own transformation images and apply them to the different left and right-facing transformations! Just make sure that if you're only flipping the transform image for your left image that you reverse the order of the aliases defined in your transform_left.animation, so that they play in the proper order when called in-game.

    You can also remove the white fade effect from the transition pretty simply. All you have to do is delete any line referencing "fade" from your sphere tech's .lua file, and any derived from it. You could also just tweak the color by editing this line here:
    Code:
    animator.setGlobalTag("ballDirectives", string.format("?fade=FFFFFFFF;%.1f", math.min(1.0, self.transformFadeTimer / (self.transformFadeTime - 0.15))))
    The row of Fs you see there sets the color and brightness, following RGB-L format. Put all of this together, and you wind up with something unique to your tech that you can edit as you please!
    https://gyazo.com/e103c29e16cbae8d3d21b77e62bccf41


    Bug tax:
    https://gyazo.com/a394d9de38a83f18bb9af148d2bd785b
     
    Last edited: Jan 6, 2017
    charity236 likes this.
  2. EmissaryOfInfinity

    EmissaryOfInfinity Subatomic Cosmonaut

    That was quite a journey...four edit's worth, to be exact. And still only able to replicate Vanilla.
     
    IHart likes this.
  3. EmissaryOfInfinity

    EmissaryOfInfinity Subatomic Cosmonaut

    Got it figured out. Will post results and findings in the morning when I'm less tired. Next step: Remove the white fade effect.

    Edit: Removed the white fade in a matter of minutes, working up OP now.
     
    Last edited: Jan 6, 2017
    charity236 and IHart like this.
  4. Storm_UK

    Storm_UK Existential Complex

    Theres also animator.setFlipped() which could be useful in this instance.
    Code:
      animator.setFlipped(mcontroller.facingDirection() < 0)
    
    That should flip the animation when the mcontroller is facing left.
     
  5. EmissaryOfInfinity

    EmissaryOfInfinity Subatomic Cosmonaut

    Yeah, I noticed that when I started digging through the code of the Dash abilities for use with the Speed Booster, and I plan to streamline my code at some point to also use that feature. The biggest problem I'm seeing when it comes to Starbound modding is the lack of "proper" tutorials, and of comments in code that explain what the different bits do, which led to me not knowing how to set up the arguments or this function for the longest time. I probably won't be the first person to do it, but I intend to help break this trend by making thorough comments on my code for the inevitable folks who unpack my work later.
     
  6. EmissaryOfInfinity

    EmissaryOfInfinity Subatomic Cosmonaut

    Okay, for anyone else coming here, do not use animator.setFlipped(), as it will carry over to the Distortion Sphere. You'd have to find some way to actively fix that issue while morphed.
     

Share This Page