Tutorial How to make a Pet

Discussion in 'Starbound Modding' started by Campaigner, Jun 13, 2015.

?

Did I explain this well enough for you to understand?

  1. Yes

    3 vote(s)
    60.0%
  2. Yes, but it was a little difficult

    1 vote(s)
    20.0%
  3. No (please post why so I can touch it up)

    1 vote(s)
    20.0%
  1. Campaigner

    Campaigner Giant Laser Beams

    This guide is under the assumption that you have at least basic knowledge of spriting. If you do not know how to create sprites, look up guides across the internet. There's many that can teach you how to do this. I will offer advice, but this is not a topic on how to do pixel art.

    =====================
    I. Introduction
    =====================


    DIFFICULTY: BEGINNER/INTERMEDIATE

    In this simple no-picture guide, I will be teaching you how to create a custom pet spawning object from scratch. In this tutorial, you will get a basic understanding of the files required to create your new friend's spawning object. You will need the following programs;

    - Image Editing Software that supports PNG and transparency (Photoshop, GIMP, etc)
    - Notepad ++ (Not to be confused with Notepad, a program most computers already have installed)
    - Internet Browser of choice
    -- Google "animal locomotion" to figure out how animals move. Helps a lot when spriting!

    This guide may seem difficult, but it is not. I spent about an hour fiddling with everything until I got it done, and now I have a guideline for everything set out so that any future pets I make can be done in minutes. Doing this the first time will be the slowest, but any you make after can be carbon copies of your first! How great the Copy and Paste functions are! The hardest part is making your pet's images, so take your time and do those properly. Don't be in a rush, don't settle for anything but what YOU want, and most importantly; do not get frustrated. Patience is REQUIRED.

    =====================
    II. The Monster
    =====================


    Let us create a folder path. In your "Starbound/giraffe_storage/mods" folder, create your mod's folder. For this example, I'll use "TESTPET" as my mod's name. The folder's location should be as such;
    "Starbound/giraffe_storage/mods/TESTPET".

    Next, we create a chain of folders that ends up like so;
    "Starbound/giraffe_storage/mods/TESTPET/monsters/pets/PETNAME/"

    "PETNAME" is the folder that will house your pet's files. Be sure to name it appropriately, such as "pink rabbit" or "orange transparent chainsaw", or whatever your critter will be called. For the rest of the tutorial, I will use PETNAME in place of an actual pet name, but you will use what you want it to be called. Folders can have spaces in the names (Pet Dog), but files need underscores (pet_dog.filetype). Keep this in mind.

    Now, we need to create our pet. I won't go through the details on how to MAKE the sprites, but here are the basic guidelines;

    - Each frame must be 32x32 pixels, or any increment of 32 (64, 96, 128, etc). They must be square in shape.
    - There must be at least one frame for idle, standing, blinking, walking, running, jumping, falling, swimming, sleeping, eating, inspecting, and howling.
    - Organize! Don't let your frames get all jumbled.
    - It must be saved as a PNG with a transparent background.

    Once you have your pet's PNG made, let's make their frames file. Here is an excerpt from the base pet files;

    Code:
    {
      "frameGrid" : {
      "size" : [32, 32],
      "dimensions" : [9, 6],
    
      "names" : [
      [ null, "idle.1", null, "stand.1", null, "run.1", "run.2", "run.3", "run.4" ],
      [ null, "blink.1", null, "jump.1", "jump.2", "jump.3", null, "fall.1", "fall.2" ],
      [ null, "walk.1", "walk.2", "walk.3", "walk.4", "walk.5", "walk.6", null, null ],
      [ null, "eat.1", "eat.2", "eat.3", null, "inspect.1", "inspect.2", "inspect.3", "inspect.4" ],
      [ null, "swim.1", "swim.2", "swim.3", "swim.4", "swim.5", "swim.6", null, null ],
      [ null, "sound.1", "sound.2", "sound.3", "sound.4", null, "sleep.1" ]
      ]
      }
    }
    


    Here is a breakdown of the importance of each part;
    "size" is the size of EACH INDIVIDUAL FRAME. Again, your frames need to be in increments of 32x32.
    "dimensions" is how many columns and rows you have in your pet's frames. In this example, the most frames in one row is 9.
    "names" dictates what each frame is called in your PNG. Each frame has ".1" or a similar number at the end. This is to let the game know "this animation has this many frames"
    The "null" parts are used to fill in the blank spaces that are not used in your image. Fill in every blank area with null. Note that null has no quotation marks.

    Let's say your pet's frames are 6x6 instead, and only has two frames for each part. Each row would look like this;

    [ "idle.1","stand.1", "run.1", "run.2", null, null ],

    Set up your frames file in Notepad ++, organizing each frame to match your PNG's frame locations. Don't forget the null values! When you are done, save the file as "PETNAME.frames". Make sure the file's extension is .frames, not .txt. Notepad++ saves as txt by default. If the file ends in .txt, simply rename the file and accept any popups.

    Go ahead and close that, and let's make a new file. This one will be PETNAME.animation.

    Code:
    {
      "animatedParts" : {
      "stateTypes" : {
      "movement" : {
      "priority" : 0,
      "default" : "idle",
      "states" : {
      "idle" : {
      "frames" : 1
      },
      "stand" : {
      "frames" : 1
      },
      "run" : {
      "frames" : 4,
      "cycle" : 0.25,
      "mode" : "loop"
      },
      "blink" : {
      "frames" : 1
      },
      "walk" : {
      "frames" : 6,
      "cycle" : 1,
      "mode" : "loop"
      },
      "jumping" : {
      "frames" : 3,
      "cycle" : 0.4,
      "mode" : "loop"
      },
      "falling" : {
      "frames" : 2,
      "cycle" : 0.4,
      "mode" : "loop"
      },
      "eat" : {
      "frames" : 3,
      "cycle" : 0.375
      },
      "inspect" : {
      "frames" : 4,
      "cycle" : 0.5
      },
      "swim" : {
      "frames" : 6,
      "cycle" : 0.6
      },
      "sound" : {
      "frames" : 4,
      "cycle" : 0.5,
      "mode" : "transition",
      "transition" : "stand"
      },
      "sleep" : {
      "frames" : 1
      }
      }
      },
    
      "portrait" : {
      "priority" : -1,
      "default" : "portrait",
      "states" : {
      "portrait" : {
      "frames" : 1
      }
      }
      }
      },
    
      "parts" : {
      "body" : {
      "properties" : {
      "centered" : true,
      "offset" : [0, 1]
      },
      "partStates" : {
      "movement" : {
      "idle" : {
      "properties" : {
      "image" : "<partImage>:idle.<frame>"
      }
      },
      "stand" : {
      "properties" : {
      "image" : "<partImage>:stand.<frame>"
      }
      },
      "run" : {
      "properties" : {
      "image" : "<partImage>:run.<frame>"
      }
      },
      "blink" : {
      "properties" : {
      "image" : "<partImage>:blink.<frame>"
      }
      },
      "walk" : {
      "properties" : {
      "image" : "<partImage>:walk.<frame>"
      }
      },
      "jumping" : {
      "properties" : {
      "image" : "<partImage>:jump.<frame>"
      }
      },
      "falling" : {
      "properties" : {
      "image" : "<partImage>:fall.<frame>"
      }
      },
      "eat" : {
      "properties" : {
      "image" : "<partImage>:eat.<frame>"
      }
      },
      "inspect" : {
      "properties" : {
      "image" : "<partImage>:inspect.<frame>"
      }
      },
      "swim" : {
      "properties" : {
      "image" : "<partImage>:swim.<frame>"
      }
      },
      "sound" : {
      "properties" : {
      "image" : "<partImage>:sound.<frame>"
      }
      },
      "sleep" : {
      "properties" : {
      "image" : "<partImage>:sleep.<frame>"
      }
      }
      },
    
      "portrait" : {
      "portrait" : {
      "properties" : {
      "image" : "<partImage>:idle.1"
      }
      }
      }
      }
      }
      }
      },
    
      "particleEmitters" : {
      "damage" : {
      "emissionRate" : 0.7,
      "particles" : [
      ]
      },
      "deathPoof" : {
      "particles" : [
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/puff2c/puff2c.animation",
      "size" : 1,
      "angularVelocity" : 35,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [0, 0],
      "finalVelocity" : [0, 0],
      "approach" : [1, 1],
      "timeToLive" : 0.4,
      "layer" : "middle"
      }
      },
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/fizz1/fizz1.animation",
      "size" : 1,
      "angularVelocity" : 20,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [-8, 8],
      "finalVelocity" : [-3, -4],
      "approach" : [15, 15],
      "timeToLive" : 3.45,
      "layer" : "middle",
      "variance" : {
      "initialVelocity" : [-4, 2],
      "finalVelocity" : [-3, -4]
      }
      }
      },
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/fizz1/fizz1.animation",
      "size" : 1,
      "angularVelocity" : 20,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [8, 8],
      "finalVelocity" : [3, -4],
      "approach" : [15, 15],
      "timeToLive" : 3.45,
      "layer" : "middle",
      "variance" : {
      "initialVelocity" : [4, 2],
      "finalVelocity" : [3, -4]
      }
      }
      },
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/fizz2/fizz2.animation",
      "size" : 1,
      "angularVelocity" : 20,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [-8, 8],
      "finalVelocity" : [-3, -4],
      "approach" : [15, 15],
      "timeToLive" : 3.45,
      "layer" : "middle",
      "variance" : {
      "initialVelocity" : [-4, 2],
      "finalVelocity" : [-3, -4]
      }
      }
      },
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/fizz2/fizz2.animation",
      "size" : 1,
      "angularVelocity" : 20,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [8, 8],
      "finalVelocity" : [3, -4],
      "approach" : [15, 15],
      "timeToLive" : 3.45,
      "layer" : "middle",
      "variance" : {
      "initialVelocity" : [4, 2],
      "finalVelocity" : [3, -4]
      }
      }
      },
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/fizz3/fizz3.animation",
      "size" : 1,
      "angularVelocity" : 20,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [-8, 8],
      "finalVelocity" : [-3, -4],
      "approach" : [15, 15],
      "timeToLive" : 3.45,
      "layer" : "middle",
      "variance" : {
      "initialVelocity" : [-4, 2],
      "finalVelocity" : [-3, -4]
      }
      }
      },
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/fizz3/fizz3.animation",
      "size" : 1,
      "angularVelocity" : 20,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [8, 8],
      "finalVelocity" : [3, -4],
      "approach" : [15, 15],
      "timeToLive" : 3.45,
      "layer" : "middle",
      "variance" : {
      "initialVelocity" : [4, 2],
      "finalVelocity" : [3, -4]
      }
      }
      },
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/fizz4/fizz4.animation",
      "size" : 1,
      "angularVelocity" : 20,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [-8, 8],
      "finalVelocity" : [-3, -4],
      "approach" : [15, 15],
      "timeToLive" : 3.45,
      "layer" : "middle",
      "variance" : {
      "initialVelocity" : [-4, 2],
      "finalVelocity" : [-3, -4]
      }
      }
      },
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/fizz4/fizz4.animation",
      "size" : 1,
      "angularVelocity" : 20,
      "fade" : 1,
      "destructionTime" : 7,
      "position" : [0, 0],
      "initialVelocity" : [8, 8],
      "finalVelocity" : [3, -4],
      "approach" : [15, 15],
      "timeToLive" : 3.45,
      "layer" : "middle",
      "variance" : {
      "initialVelocity" : [4, 2],
      "finalVelocity" : [3, -4]
      }
      }
      }
      ]
      },
    
      "emotehappy" : {
      "emissionRate" : 1,
      "particles" : [
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/emotes/happy.animation",
      "position" : [0.5, 2],
      "finalVelocity" : [0, 0],
      "initialVelocity" : [0, 0],
      "destructionTime" : 0.2,
      "destructionAction" : "shrink",
      "layer" : "front",
      "timeToLive" : 0.8,
      "flippable" : false
      }
      }
      ]
      },
    
      "emotesad" : {
      "emissionRate" : 1,
      "particles" : [
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/emotes/sad.animation",
      "position" : [0.5, 2],
      "finalVelocity" : [0, 0],
      "initialVelocity" : [0, 0],
      "destructionTime" : 0.2,
      "destructionAction" : "shrink",
      "layer" : "front",
      "timeToLive" : 0.8,
      "flippable" : false
      }
      }
      ]
      },
    
      "emoteconfused" : {
      "emissionRate" : 1,
      "particles" : [
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/emotes/confused.animation",
      "position" : [0.5, 2],
      "finalVelocity" : [0, 0],
      "initialVelocity" : [0, 0],
      "destructionTime" : 0.2,
      "destructionAction" : "shrink",
      "layer" : "front",
      "timeToLive" : 0.8,
      "flippable" : false
      }
      }
      ]
      },
    
      "emotesleepy" : {
      "emissionRate" : 1,
      "particles" : [
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/emotes/sleepy.animation",
      "position" : [0.5, 2],
      "finalVelocity" : [0, 0],
      "initialVelocity" : [0, 0],
      "destructionTime" : 0.2,
      "destructionAction" : "shrink",
      "layer" : "front",
      "timeToLive" : 0.8,
      "flippable" : false
      }
      }
      ]
      },
    
      "sleep" : {
      "emissionRate" : 2,
      "particles" : [
      {
      "particle" : {
      "type" : "animated",
      "animation" : "/animations/statuseffects/sleep/sleep.animation",
      "position" : [0, 0],
      "initialVelocity" : [0, 3],
      "finalVelocity" : [10, 1],
      "approach" : [2, 50],
      "size" : 1,
      "layer" : "middle",
      "timeToLive" : 9,
      "flippable" : false,
      "variance" : {
      "size" : 0.3
      }
      }
      }
      ]
      }
      },
    
      "effects" : {
      "blink" : {
      "type" : "flash",
      "time" : 0.25,
      "directives" : "fade=ffffff;0.5"
      }
      },
    
      "sounds" : {
      "turnHostile" : [ ],
      "deathPuff" : [ "/sfx/npc/enemydeathpuff.wav" ],
      "petsound" : []
      }
    }
    
    


    This one is a doozy, but we only need to worry about a small part of it! Copy and paste the above into your PETNAME.animation file, and let's begin changing some values. Take a look at my "idle" and "run" values first. Idle has one frame, and Run has four with "cycle" and "loop" values. If any of your frames have multiple parts, you want to include a cycle. This lets it animate through each frame. The cycle's value is in seconds, so if you want each frame to be 1 second long, you would have "cycle: 1". If you want the frames to be half a second, you would use 0.5. Set the speeds accordingly to how you imagine your pet's speed. When we do our initial testing, feel free to come back and edit the values. A loop is not necessary for some frames, such as inspecting, but for things like running and walking, you want it to loop. All you are aiming to do for this part is changing each value for the amount of frames in each animated section. You can safely ignore everything after your "sleep" value is edited. Once your values match the amount of frames your pet's PNG and .frames file have, save and make sure the file extension is .animation.

    Now we're on to the easiest part; the body. Make a file called "body.monsterpart", and copy the following into the file;

    Code:
    {
      "name" : "body",
      "category" : "testpet",
      "type" : "body",
      "frames" : {
      "body" : "testpet.png"
      }
    }
    


    This one is simple. name the category to be the same as your pet's name, and have the "body" value change to match your png file. If you've been following along properly, everthing should essentially have the same name. We're almost done making the beast itself, but we have one big hurdle to finish; the actual pet's monstertype file. Create a "TESTPET.monstertype" file, making sure the extension is monstertype, and copy the following into the file;

    Code:
    {
      "type" : "testpet",
    
      "categories" : [ "testpet" ],
      "parts" : [ "body" ],
    
      "animation" : "testpet.animation",
    
      "dropPools" : [ ],
    
      "baseParameters" : {
      "persistent" : true,
      "damageTeamType" : "ghostly",
    
      "scripts" : [
      "/monsters/pets/groundPet.lua",
      "/scripts/pathing.lua",
      "/scripts/stateMachine.lua",
      "/scripts/util.lua",
      "/scripts/vec2.lua",
    
      "/monsters/pets/idleState.lua",
      "/monsters/pets/wanderState.lua",
    
      "/monsters/pets/petBehavior.lua",
      "/monsters/pets/actions/followAction.lua",
      "/monsters/pets/actions/inspectAction.lua",
      "/monsters/pets/actions/eatAction.lua",
      "/monsters/pets/actions/sleepAction.lua",
      "/monsters/pets/actions/begAction.lua",
      "/monsters/pets/actions/pounceAction.lua",
      "/monsters/pets/actions/starvingAction.lua"
      ],
    
      "petBehavior" : "petBehavior",
    
      "anchorName" : "testpetanchor",
      "petResources" : {
      "sleepy" : 10,
      "hunger" : 60,
      "playful" : 10,
      "curiosity" : 60
      },
      "petResourceDeltas" : {
      "sleepy" : 1,
      "hunger" : 0.5,
      "playful" : 1,
      "curiosity" : 1
      },
    
      "actionParams" : {
      "hungerStarvingLevel" : 80,
      "beg" : {
      "minScore" : 50,
      "cooldown" : 5,
      "distance" : 3,
      "emoteCooldown" : 2
      },
      "follow" : {
      "minScore" : 35,
      "cooldown" : 5,
      "curiosityDelta" : -5,
      "boredTime" : 3
      },
      "inspect" : {
      "minScore" : 20,
      "cooldown" : 2
      },
      "eat" : {
      "minScore" : 0,
      "minHunger" : 40,
      "cooldown" : 0,
      "distance" : 2
      },
      "play" : {
      "minScore" : 20,
      "cooldown" : 2
      },
      "sleep" : {
      "minScore" : 50,
      "minSleepy" : 75,
      "cooldown" : 10
      },
      "starving" : {
      "minScore" : 60,
      "cooldown" : 3
      }
      },
    
      "pathing" : {
      "canOpenDoors" : false
      },
    
      "metaBoundBox" : [-1.625, -2.375, 1.75, 2.0],
      "scale" : 1.0,
    
      "querySurroundingsCooldown" : 1,
    
      "wander" : {
      "wanderTime" : [5, 10],
      "changeDirectionTime" : [5, 10],
      "changeDirectionWait" : [1, 3]
      },
    
      "pounce" : {
      "maxRange" : 10,
      "minRange" : 5
      },
    
      "idle" : {
      "idleTime" : [4, 8],
      "standTime" : 2
      },
    
      "movementSettings" : {
      "collisionPoly" : [ [-0.75, -1], [0.75, -1], [0.75, 0.5], [-0.75, 0.5] ],
    
      "mass" : 1.0,
      "walkSpeed" : 4,
      "runSpeed" : 14,
      "flySpeed" : 15,
      "airForce" : 50.0
      },
    
      "bodyMaterialKind" : "organic",
    
      "knockoutTime" : 0,
      "knockoutEffect" : "blink",
      "deathParticles" : "deathPoof",
    
      "touchDamage" : {
      "poly" : [ [-1.625, -2.375], [1.75, -2.375], [1.75, 2.0], [-1.625, 2.0] ],
      "damage" : 15,
    
      "teamType" : "enemy",
      "damageSourceKind" : "lash",
      "statusEffects" : [ ]
      },
    
      "dropPools" : [ "potreasure" ],
    
      "statusSettings" : {
      "statusProperties" : {
      "targetMaterialKind" : "organic"
      },
    
      "appliesEnvironmentStatusEffects" : false,
      "minimumLiquidStatusEffectPercentage" : 0.1,
    
      "primaryScriptSources" : [
      "/stats/monster_primary.lua"
      ],
      "primaryScriptDelta" : 0,
    
      "stats" : {
      "maxHealth" : {
      "baseValue" : 72
      },
      "protection" : {
      "baseValue" : 1.0
      },
      "healthRegen" : {
      "baseValue" : 0.0
      }
      },
    
      "resources" : {
      "health" : {
      "maxStat" : "maxHealth",
      "deltaStat" : "healthRegen",
      "defaultPercentage" : 100
      },
      "sleepy" : {
      "maxValue" : 100,
      "defaultPercentage" : 10
      },
      "hunger" : {
      "maxValue" : 100,
      "defaultPercentage" : 10
      },
      "playful" : {
      "maxValue" : 100,
      "defaultPercentage" : 10
      },
      "curiosity" : {
      "maxValue" : 100,
      "defaultPercentage" : 10
      }
      }
      },
    
      "mouthOffset" : [0, 0],
      "feetOffset" : [0, -8]
      }
    }
    
    


    HOLY SMOKES, LOOK AT ALL THAT CODING! You're probably thinking "What, you think I've got a degree in coding? You're nuts!", but this is just an illusion of depth; we're only editing a small amount of this! Here is what we will edit;


    - "type" : "testpet",
    - "categories" : [ "testpet" ],
    - "parts" : [ "body" ],
    - "animation" : "testpet.animation",
    - "anchorName" : "testpetanchor",

    as with previous parts, simply change my "testpet" values to match your pet's values. All of these values, except for "body" will be the same name. But wait, what is this "anchorName" value? Let's leave that for the next part of the tutorial. Edit just the above values to match your own, and save your PETNAME.monstertype file.

    Hooray, we did the hard part! You have successfully created a pet! Now, your files should look like this in your folder (if you sort by name):

    - body.monsterpart
    - testpet.animation
    - testpet.frames
    - testpet.monstertype
    - testpet.png

    If you have all of this in order, and you have everything done from this section, then you're ready to move on to making the pet's spawning object!

    =====================
    III. Pet Spawner
    =====================


    So you've got a monster, and I'm sure you've tried spawning it with /spawnmonster testpet", only to find that it instantly disappears when spawned. In the previous section, we had a value called "anchorName" that didn't have a visible purpose yet. Now, what do you think of when you hear "anchor"? If you are nautically knowledgeable, you know that anchors keep things in place. What we're doing is making an "anchor" for our pet to stay spawned.

    Now, you can put the folder into any location you want, but in my personal opinion, I'd keep the files next to the pet. So, create a folder inside either of these locations;

    "Starbound/giraffe_storage/mods/TESTPET/monsters/pets/PETNAME/PETANCHOR"
    "Starbound/giraffe_storage/mods/TESTPET/monsters/pets/PETANCHOR/"

    This is just so they're close together. Don't put all the files from the pet and our soon-to-be object together, as that becomes clustered and unsightly. Nobody likes disorganization!

    In the PETANCHOR folder, let's make our png files. That's right, multiple. For this example, we're doing two files with one frame each; the base object, and its inventory icon. Your object can be any size you want it to be, but the inventory icon should be 16x16 pixels. For the sake of simplicity, name your icon file "icon.png", and your object "PETNAMEanchor.png", replacing PETNAME with the same value you used for every file in the previous section. I'll continue using "testpetanchor" as mine, but make sure you name yours as what you want it to be!

    Once you finished making your images, let's begin making the item's files. First create a text file called "testpetanchor.frames", make sure it's extension is .frames, and copy the following into the file;

    Code:
    {
      "frameGrid" : {
      "size" : [16, 16],
      "dimensions" : [1, 1],
      "names" : [
      [ "default.1" ]
      ]
      },
      "aliases" : {
      "default.default" : "default.1"
      }
    }
    


    Like the frames file for your pet, the frames in your petanchor should match the image you made for the object. My example is 16x16, but you can use anything. I'd SUGGEST anything with even numbers, but you can do odd. My image has only one frame, so we won't be doing much animating this time. If you want to animate your image, you would do something like so;

    Code:
    {
      "frameGrid" : {
      "size" : [16, 16],
      "dimensions" : [4, 1],
      "names" : [
      [ "default.0", "default.1", "default.2", "default.3" ]
      ]
      },
      "aliases" : {
      "default.default" : "default.0"
      }
    }
    


    When animating objects, the first frame is always ".0", the second ".1", and so on. Of course, your object needs to have every frame in the same dimensions, so my "4,1" means I'm using 64x16 as my image dimensions. Once you've set your frames file up, make sure its file extension is .frames, and let's move on to the animation. Create a "testpetanchor.animation" file, and copy the following:

    Code:
    {
      "animatedParts" : {
      "stateTypes" : {
      "beaconState" : {
      "default" : "idle",
      "states" : {
      "idle" : {
      "frames" : 1,
      "cycle" : 0.15
      },
      "active" : {
      "frames" : 1,
      "cycle" : 0.7,
      "mode" : "loop"
      }
      }
      }
      },
      "parts" : {
      "beacon" : {
      "properties" : {
      "centered" : false
      },
    
      "partStates" : {
      "beaconState" : {
      "idle" : {
      "properties" : {
      "image" : "testpetanchor.png"
      }
      },
      "active" : {
      "properties" : {
      "image" : "testpetanchor.png"
      }
      }
      }
      }
      }
      }
      }
    }
    


    Simply replace the "testpetanchor.png" values to match your png's filename. If you have multiple frames, change the "active" value to whatever you need. Like the monster's animation, change the cycle to match your desired speed. With that out of the way, let's make the most important file next; "testpetanchor.object". Again, make this file, and copy the following into it;

    Code:
    {
      "objectName" : "testpetanchor",
      "tags" : ["misc","pretty","valuable"],
      "rarity" : "Uncommon",
      "description" : "This object spawns a pet when placed!",
      "shortdescription" : "TESTPET Spawner",
      "race" : "generic",
      "category" : "tools",
      "printable" : false,
      "inventoryIcon" : "icon.png",
      "orientations" : [
      {
      "image" : "testpetanchor.png:<color>.<frame>",
      "imagePosition" : [0, 0],
        "flipImages" : false,
      "frames" : 1,
      "animationCycle" : 1,
      "spaceScan" : 0.1,
      "anchors" : [ "background" ],
        "direction" : "right"
      }
      ],
      "animation" : "testpetanchor.animation",
      "animationParts" : {
      "beacon" : "testpetanchor.png"
      },
      "animationPosition" : [0, 0],
      "scripts" : [ "/objects/spawner/shipPetSpawner.lua" ],
      "scriptDelta" : 5,
      "shipPetType" : "testpet",
      "spawnOffset" : [0, 2]
    }
    


    We've got a bit to explain here.

    - "objectName" is the object's ID. When we use /spawnitem, we'll use this name.
    - "tags" are for the upcoming colony patch. Don't worry about this for now.
    - "rarity" is the item's border color. There are common (white), uncommon (green), rare (blue), and legendary (purple) values.
    - "description" is the flavor text of the item.
    - "shortdescription" is the item's name in game.
    - "race" is not really needed, but it can be used for the upcoming colony patch.
    - "category" doesn't make any difference. I kept it as "tools" because it was from what I copied way back when I first did this.
    - "printable" determines if you can print it at the 3D printer or not. "false" for no, "true" for yes.
    - "inventoryIcon" is the icon.png file from earlier.
    - "image" is the png file for our object.
    - "imagePosition" is where the object gets placed. at 0, 0, it is placed by the bottom left corner.
    - "animationCycle" should match your animation cycle value from the .animation folder.
    - "anchors" determines where your item can be placed. "bottom" for ground only, "background" for walls, and "top" for ceiling only.
    - "shipPetType" is the pet you made. You will use the "type" value from the monstertype file.
    - "spawnOffset" is where the pet spawns in relation to the object. I set it to spawn two pixels up from the center of the object.

    Now, as with all of the other files, we want the "testpet" values to match your pet's names. the "objectName" is the "anchorName" from the monstertype file. If you want it to be animated, simply change the frames and animationCycle values to match your object's frames. Don't forget to make sure the .animation file has the same amount of frames and cycle!

    Save everything, and let's see if you have everything right so far;

    - testpetanchor.animation
    - testpetanchor.frames
    - testpetanchor.object
    - testpetanchor.png
    - icon.png

    If your files look like this, then you did everything right! Now, I'll skip the recipe file for now, but you can make one of those if you want. Let's move on to the fun part; testing it out!

    =====================
    IV. Testing
    =====================


    You will not need a .patch file for this at all. However, you will need a modinfo file in the mod folder. You can't load a mod without this!
    Code:
    {
      "name" : "testpetmod",
      "version" : "Beta v. XXX Giraffe",
      "path" : ".",
      "dependencies" : [],
      "metadata" : {
      "author" : "You",
      "description" : "Something something something",
      "support_url" : ""
      }
    }
    
    Make the "name" unique, set the "version" to be the current game's version (found on the bottom right of the game's start menu", and make sure the author is your name. The description can be left blank, because nobody will see it anyway. I suggest giving it a description anyway for the sake of completion. Put the new file into the main mod folder; "Starbound/giraffe_storage/mods/TESTPET". Of course, your folder (hopefully) will have the name of your pet, or your mod in general.

    With all that set up, load a character. In game, turn on admin commands (/admin), and type "spawnitem testpetanchor" without quotation marks. If a Perfectly Generic Item appears, you did not follow my guide right. In this case, navigate to the "Starbound/giraffe_storage/" folder, and open "starbound.log" in Notepad ++. If you do not understand the error, post it in the thread and someone (likely me) will help you fix it.

    Should you get the item properly instead of a Perfectly Generic Item, place it and check to make sure it works. If a pet spawns, runs over to you, does "?" and then "<3", then you did it right! If it blinks and has some frames go invisible, you missed a step or did not add a value somewhere. In this case and if you can't figure it out, post your "starbound.log", "testpet.animation", and "testpet.frames" files so I can spot the issue for you. Make sure to have spoiler tags so it does not cause the page to be hilariously long!

    If you did everything right, you now have a fun little pet that follows you around and begs for food. Do note that, if the object is removed from where it is placed, the pet will despawn. This is because its anchor is now missing. You can place the anchor anywhere, and the pet will follow you so long as it doesn't get bored while following you.


    =====================
    V. Known Issues
    =====================


    - As of June 13 on the Nightly, there is an issue with the pet's sleeping script. You may crash if your pet tries to sleep. I do not know why it does this. This issue does not exist on the Stable branch.

    - Blinking frames (not for the blink animation) are the result of incorrect values in your frames and animation files. Make sure they're the same!
     
  2. Roskii Heiral

    Roskii Heiral Heliosphere

    I know this is an old post, but I wanted to say thank you so much!
     
    Waffle-Chan likes this.

Share This Page