Modding Help Why won't this work? Please help me.

Discussion in 'Starbound Modding' started by @lias, Apr 19, 2017.

  1. @lias

    @lias Void-Bound Voyager

    Yeah, it'd probably be the smart thing to step back from this for a while and work on smaller things, especially since I'm now almost positive this is impossible within the current system.
    I'll try putting together something silly and simple, it'll be fun. Thanks a ton for being so patient with me.



    Before I do, though, here are some final thoughts on what I found after more combing, in case anyone else is interested.
    There are no mentions to arm rotation, offset, handposition, or anything of that manner in the decompiled playerfile. The closest thing is the "personality" section, which contains a number of things related to character customization, including offsets that I initially thought were the values I was looking for, but actually have to do with head and arm offsets as part of the personality.
    Code:
        "identity" : {
          "facialMaskGroup" : "fin",
          "name" : "test",
          "gender" : "female",
          "personalityIdle" : "idle.4",
          "species" : "orcana",
          "personalityArmIdle" : "idle.1",
          "bodyDirectives" : "?replace;c7815b=262626;d39c6c=343434;ffc181=505050;ffe2c5=6f6f6f?replace;951500=6f6f6f;be1b00=aeaeae;dc1f00=d7d7d7;ff4224=fafafa",
          "emoteDirectives" : "?replace;c7815b=262626;d39c6c=343434;ffc181=505050;ffe2c5=6f6f6f?replace;951500=6f6f6f;be1b00=aeaeae;dc1f00=d7d7d7;ff4224=fafafa",
          "facialHairGroup" : "",
          "personalityArmOffset" : [0, 0],
          "hairGroup" : "pattern",
          "hairType" : "3",
          "hairDirectives" : "?replace;951500=6f6f6f;be1b00=aeaeae;dc1f00=d7d7d7;ff4224=fafafa",
          "facialHairType" : "",
          "facialHairDirectives" : "",
          "facialMaskType" : "14",
          "facialMaskDirectives" : "?replace;c7815b=262626;d39c6c=343434;ffc181=505050;ffe2c5=6f6f6f?replace;951500=6f6f6f;be1b00=aeaeae;dc1f00=d7d7d7;ff4224=fafafa",
          "personalityHeadOffset" : [0, 0],
          "color" : [51, 117, 237]
        },
    
    As you can see, pretty much all the things relating to character customization are there, down to the color, name, and most importantly... species. I have a theory, and it's pretty well backed up by my prior findings that the only things capable of changing anything relating to arms were the humanoid.config files. But, the file that points to the humanoid.config file in the first place is the species file!

    Therefore, due to the lack of any information defined in the humanoid.configs present in the player file, I hypothesize that everything I've been trying to find and change is all under the jurisdiction of that one single "species" line. Instead of wasting space storing it somewhere easily editable, I believe they just have it reference the species file and by extension the associated humanoid.config, because who's ever going to need to change it, right?

    This theory is all but confirmed by this video,

    In which a player edits the identity section of his player file exclusively to change his character's species, appearance, and name, to no ill effects.

    Which means that, unless there's a way to find and replace a string in the player file with lua while the game is running, this mission was shot from the beginning.

    The closest thing I've found while looking was this,
    Code:
    -- find and replace a value buried in an opaque heap of structure
    function replaceInData(data, keyname, value, replacevalue)
      if type(data) == "table" then
        for k, v in pairs(data) do
          if (k == keyname or keyname == nil) and (v == value or value == nil) then
            -- sb.logInfo("Replacing value %s of key %s with value %s", v, k, replacevalue)
            data[k] = replacevalue
          else
            replaceInData(v, keyname, value, replacevalue)
          end
        end
      end
    end
    
    but I doubt it does what I think it does and also probably isn't something I should be playing around with.

    TL-DR, this mission was doomed from the get-go. But I learned some things, so it's OK.
     
    Cyel likes this.
  2. @lias

    @lias Void-Bound Voyager

    OK, so I've been fooling around and making different silly things to help familiarize myself with business in general and try to have a good time. Predictably, I'm stuck on something again. It should be less... impossible than the other thing was, though.

    One of the things I'm working on is a hat that puts itself back on whenever you take it off. It's evil! Or cursed, or something. Anyway, I figure to do that, I'll need some way to check what the player is wearing, check it against what they should be wearing, and equip the correct thing in the correct slot if they're not wearing it. I've got a general game plan on how to do it, but my understanding and execution is lacking.

    Here's what I've got so far.
    Code:
    function init()
      self.myHat = player.equippedItem({}, "head")
      self.curseHat = ("badhat", "head")
    end
    
    function update(dt)
      if self.myHat ~= self.curseHat
      then player.setEquippedItem("badhat", "head")
      elseif self.myHat == self.curseHat
      then
      end
    end
    
    Obviously, it doesn't work.
    I'll walk you through my thought process so hopefully you can tell me where I went wrong. From the files I've looked at, init usually defines values if it's a more complicated file (and .self serves as some kind of short term storage?), so I tried to do that. Originally I was using local to define values before realizing that it only works in the chunk it's in, hence local. I felt pretty stupid after that round of monkey see, monkey do.

    I'm not 100% sure on the update chunk but I think it's where the status is repeatedly checked so it doesn't just run once, which wouldn't be very useful for status effects. Is it not working because it thinks I'm trying to compare tables, and that's what player.equippedItem comes back as? Do I have to convert them into numerical values, like self.myhat = 1 and self.cursehat = 2, and if 1 ~= 2 then set equippeditem, or can you compare strings?

    Also, the log isn't happy with the init block. It's mad at me for
    Code:
    attempt to index a nil value (global 'player')
    
    The doc has this to say about the function;
    Code:
    #### `ItemDescriptor` player.equippedItem(`String` slotName)
    
    Returns the contents of the specified equipment slot, or `nil` if the slot is empty. Equipment slot names are "head", "chest", "legs", "back", "headCosmetic", "chestCosmetic", "legsCosmetic" and "backCosmetic".
    
    so I'm not sure why it's mad about .player because it seems to be part of it.

    I think I've got something backwards somewhere, and I'm not sure what ItemDescriptor is supposed to be. I figure that's what it returns when it makes the check, because the other precursor values are things like bools and strings, though I'm still in the dark as to what "void" could mean besides "Exdeath = true."
     
    Last edited: Apr 27, 2017
  3. bk3k

    bk3k Oxygen Tank

    To start with -
    init runs once at the start
    update runs pretty often

    And you aren't even checking what they're wearing in update, therefore you can't know if it changed!
    Code:
    function init()
        self.curseHat = {
        name = "badhat",
        count = 1,
        parameters = {}
      }
    end
    
    function update(dt)
      local myHat = player.equippedItem("head")
      if myHat.name ~= self.curseHat.name then
        player.setEquippedItem("head", self.curseHat)
      end
    end
    
    
    Also note you used these functions wrong per the documentation
    Code:
    #### `ItemDescriptor` player.equippedItem(`String` slotName)
    
    Returns the contents of the specified equipment slot, or `nil` if the slot is empty. Equipment slot names are "head", "chest", "legs", "back", "headCosmetic", "chestCosmetic", "legsCosmetic" and "backCosmetic".
    
    ---
    
    #### `void` player.setEquippedItem(`String` slotName, `Json` item)
    
    Sets the item in the specified equipment slot to the specified item.
    
    ---
    
    Your use of arguments does not match the documentation and that's why I changed the arguments.

    I'll recommend this through. Lets say you are using Notepad++ (which is a good idea).
    Use <ctrl> + <F> to bring open the search dialog
    Click on the "find in files" tab
    Point the directory(aka folder) to your unpacked assets.
    file type is *.lua
    For your search field try these functions you aren't sure of, like
    Code:
    player.equippedItem
    Then Find All(not replace!)
    It will search the game assets for examples of that exact string (in this case a function) within the assets. So you can look at the results to see this code in actual use/context.

    The context would tell you that an 'ItemDescriptor' isn't going to be something like "badhat" Rather it looks like this
    Code:
    {
      name = "badhat",
      count = 1,
      parameters = {}
    }
    
    Besides that, I'm not sure about the context. Is that a status applied by the hat and that lasts for a while after removing the hat?
     
    @lias likes this.
  4. Cyel

    Cyel Scruffy Nerf-Herder

    >Besides that, I'm not sure about the context. Is that a status applied by the hat and that lasts for a while after removing the hat?

    Yeah, note that a script's context limits what it gets access to; usually contexts who have access to a table are listed on top of that table's .md. For player.md:
    If you're running your script in another context (eg. a state), it'll tell you that the global "player" doesn't exist or something like that.


    To run your script in a "player companion" script, you'd have to make a player.config.patch
    Code:
    [ { "op": "add", "path" : "/statusControllerSettings/primaryScriptSources/-" , "value" : "/scripts/yourpatch.lua" } ]
    
    and have your script do something like
    Code:
    local oldInit = init
    local oldUpdate = update
    
    function init()
      oldInit()
      [...]
    end
    
    function update(dt)
      oldUpdate(dt)
      [...]
    end
    
    So that it doesn't overwrite the other script's functions running in the same context
     
    @lias likes this.
  5. @lias

    @lias Void-Bound Voyager

    Oh, doy. It wouldn't be able to replace itself it if only looked once, would it? That seems so obvious in hindsight, argh. I also had no idea that items were formatted that way, thanks!
    Speaking of the arguments in the documentation... I just now realized that the things inside the apostrophes aren't part of the code. I thought they were also values you had to define, but now I see that the leftmost ones are what the functions return, and the ones inside the functions themselves are telling you what kind of data the functions are looking for, like "string," and "json," which is what the items get turned into in this case. Another piece falls into place.

    Speaking of pieces falling into place, this is INCREDIBLE! I've been using Notepad++ and I had no idea I could do this! Now I can (and have been for the last 30 minutes) look up anything I need context on, that's invaluable. Thanks for the tip!

    You're right!. My plan was to have the hat apply apply a persistent status effect that did the above, and have it removable at a later date by way of a consumable item that removed persistent statuses, like a blessed cheeseburger or something. Unfortunately, as we see in Cyel's post...

    ...they're right! It won't work from statuses, and that's why I've been getting that error. Interface windows like the inventory and the mannequin, quest scripting(you start equipped with the protectorate armor, and this is why), and player companions(crew uniforms) are the only things that seem to use the set/equipeditem function. The stuff the mannequin does in particular sparked an idea for something down the road, but that's for later.
    Cyel, I don't fully understand it, but I'm gonna try your solution tomorrow, and hopefully I'll come to understand it in the process of doing so.

    As an aside, you guys are pretty much the best, your help and encouragement has really kept the wind in my sails. When I used to try to do things like this, I'd just end up spinning my wheels until I burned out and gave up. I never expected to get this kind of comprehensive help and support over such basic questions, and for that I thank you.
     
    Cyel likes this.
  6. Cyel

    Cyel Scruffy Nerf-Herder

  7. @lias

    @lias Void-Bound Voyager

    Cyel, I'm having a bit of trouble getting your thing to work. Could you elaborate a little on the specifics?

    I get that you have to patch your new file into the player's primary script sources so it's recognized and used (I think???), but when you say yourpatch do you mean we're pointing to the new script to be used or a literal patch file? I'm pretty much completely lost on the second part, too. Is it an entirely new file, am I piggybacking off of it with something and that's why I need to make sure the original runs too? The script for playercompanions is very large and full of confusing things and I'm not sure what I need from it besides being able to categorize the function I'm trying to use as part of it so that it gets recognized instead of throwing errors. Also, was your example part of something you used to help someone else and, if so, could you link me to the discussion so I have a better idea of context?

    On another note, since the only 3 options for accessing .player functions are interfaces, quests, and player companions, I've also been trying to think through other ways this sort of thing could be done. Are either of these possible, and if so how difficult of a solution would they be to accomplish?

    1: Because they run in quests, is there a way to give the player an "empty" quest that adds itself, does what it needs to regarding player functions, and immediately resolves itself like it was never there? I don't know how many ways you can assign quests so it might be limited in usefulness if it's even possible.

    2: Can you tie opening a GUI interface to using an item? Not an object, but a consumable or activeitem. It would be slightly more cumbersome because the player would have to navigate menus, but it might be a plus in some cases.
     
  8. Cyel

    Cyel Scruffy Nerf-Herder

    Yeah that wasn't the best filename choice; "yourpatch.lua" here should've been "yourscript.lua"; we're pointing to the new script, and it could be any name - sorry for the confusion

    The snippet I provided is a "template" script of sorts; as this script would run in the same context as the other primarySourceScripts (eg. player.lua), it'd overwrite the others scripts' functions if they have the same names. To circumvent this, we rename the other script's "update" function into "oldUpdate", then call this oldUpdate() function inside our new update() function, so that both scripts do their things; and same with init()
    Obviously it's best to get an unique name instead of "oldUpdate" as to avoid another script to overwrite it; this is only for example purposes

    It isn't really from someone else's help, but I've used taken it from a stupid mod I've made. I'm uploading it here if you wanna look at it, it's pretty basic

    Yes, the two main ways of giving quests is with an item's "pickupQuestTemplates", and by patching the .species file to add a quest to their default quest's pool. Each have their drawbacks: The first one's is that you need some player interaction to trigger the quest, the second's one is that you need to patch every .species, and that, for some reasons, if you're patching a lot of species that way, it makes the game freeze for a few seconds when interacting with a quest-giver thing.
    If you want some context / examples, two of my mods (self-plug!!) make use of it:
    http://community.playstarbound.com/threads/learn-blueprints-on-scan.131070/#post-3128625 uses the object version (and you can read some discussion about going from patching the .species file to that)
    http://community.playstarbound.com/resources/bookmarkuest.4514/ uses the .species patch version (which I'll replace eventually because of said freeze)
    Feel free to unzip them and look inside etc

    activeItems can use activeItem.interact() to open an interface. For example, this will open the tech console:
    Code:
    activeItem.interact("ScriptPane", activeItem.ownerEntityId(), "/interface/scripted/techupgrade/techupgradegui.config")
     

    Attached Files:

    Last edited: May 1, 2017

Share This Page