Modding Help powerProjectileParameters

Discussion in 'Starbound Modding' started by IHart, Jan 2, 2017.

  1. IHart

    IHart Scruffy Nerf-Herder

    prepping the grandarmory mod (not showcase) for release and ran into this slight inconvenience.
    assigning arrow projectiles to the longbow i found that projectileParameters can be defined on build but the equivalent for the power projectile cannot. in this case (https://github.com/IsaacHart/sbmod-...tive/weapons/ranged/longbow/rarelb.activeitem) im trying to use it to get each elemental arrow on a rare bow to apply the standard elemental effect. I got the regular arrow shot to apply the desired effect but the charged i have not been able to change.

    solution that i can execute easily, but is not ideal: create a new projectile file for each desired combination (edit: ) plus patching the existing projectiles

    alternate solutions that i seek, in order of preference:
    pure json just in the given file, if possible
    a lua hook, if possible
    a dirty lua edit

    TIA!
     
  2. bk3k

    bk3k Oxygen Tank

    Well I'm not an expert on the weapons per say and only 80% sure I understand the question, but I noticed this

    Code:
          "fire" : {
            "primaryAbility" : {
              "projectileType" : "flamearrow",
              "powerProjectileType" : "chargedflamearrow",
              "projectileParameters" : {
                "statusEffects" : ["burning"]
              }
            },
            "altAbility" : {
              "projectileType" : "flamearrow",
              "projectileParameters" : {
                "statusEffects" : ["burning"]
              }
            }
    },
    Your primary abilities have "powerProjectileType" but your secondary abilities do not. Try adding that under "altAbility"
    Short of that, probably using a modified version of /items/buildscripts/buildweapon.lua or hooking it. I can check out that code.
     
  3. IHart

    IHart Scruffy Nerf-Herder

    Just including "projectileType" in one and not the other functions as expected, the alt Ability does not utilize a power projectile. But I will test it anyway :rofl:
     
  4. bk3k

    bk3k Oxygen Tank

    Okay so my search for powerProjectileType only within .lua files leads only to \items\active\weapons\bow\abilities\bowshot.lua
    Currently looking at that.

    It looks like the magic happens starting on line 92
    Code:
    function BowShot:currentProjectileParameters()
      local projectileParameters = copy(self.projectileParameters or {})
      local projectileConfig = root.projectileConfig(self:perfectTiming() and self.powerProjectileType or self.projectileType)
      projectileParameters.speed = projectileParameters.speed or projectileConfig.speed
      projectileParameters.speed = projectileParameters.speed * root.evalFunction(self.drawSpeedMultiplier, self.drawTime)
      projectileParameters.power = projectileParameters.power or projectileConfig.power
      projectileParameters.power = projectileParameters.power
          * self.weapon.damageLevelMultiplier
          * root.evalFunction(self.drawPowerMultiplier, self.drawTime)
      projectileParameters.powerMultiplier = activeItem.ownerPowerMultiplier()
    
      return projectileParameters
    end
    It is going to grab the configuration of either "powerProjectileType" or "projectileType", and "powerProjectileType" will take priority here. If you had both, it would only use "powerProjectileType"

    I think I found your answer here.
    It is reading the configuration of different projectiles. The difference is that you're using a different projectile for "powerProjectileType" as compared to "projectileType". The projectile applies the status. For example under "fire"
    "chargedflamearrow"
    Code:
    {
      "projectileName" : "chargedflamearrow",
      "physics" : "arrownosticky",
      "image" : "chargedflamearrow.png",
      "animationCycle" : 0.25,
      "pointLight" : true,
      "lightColor" : [191, 103, 2],
      "frameNumber" : 3,
      "emitters" : [ "flamesfast" ],
      "damageKindImage" : "/interface/statuses/fire.png",
      "damageKind" : "bow",
      "power" : 20,
      "knockback" : 20,
      "knockbackDirectional" : true,
      "timeToLive" : 10,
      "damagePoly" : [ [8, -0.2], [8.5, -0.2], [8.5, 0.2], [8, 0.2] ],
      "statusEffects" : [
        "burning"
      ],
      "actionOnReap" : [
        {
          "action" : "config",
          "file" : "/projectiles/explosions/bulletexplosion/bulletexplosion.config"
        }
      ]
    }
    versus "flamearrow"
    Code:
    {
      "projectileName" : "flamearrow",
      "physics" : "arrow",
      "actionOnCollide" : [
        {
          "action" : "sound",
          "options" : [
            "/sfx/gun/impact_arrow.ogg"
          ]
        }
      ],
      "image" : "flamearrow.png",
      "animationCycle" : 0.25,
      "frameNumber" : 1,
      "damageKindImage" : "icon.png",
      "damageKind" : "bow",
      "power" : 20,
      "knockback" : 10,
      "knockbackDirectional" : true,
      "timeToLive" : 10,
      "damagePoly" : [ [8, -0.2], [8.5, -0.2], [8.5, 0.2], [8, 0.2] ]
    }
    "flamearrow" doesn't have a status.

    Now this means you don't need to touch any LUA. Pure JSON.
    I see what you mean now... finally.

    However my brain feels fully fried at this point because I'm clearly ready for sleep.
    But try this. I give it between 5-10% chance of not failing miserably. Surely once I wake up I'll feel stupid for thinking this would work.

    Code:
    tryThis_nonsense = {vanilla_build = build}
    
    build = function(directory, config, parameters, level, seed)
    -- preprocess shared alt attack config
      parameters.altAbility = parameters.altAbility or {}
      parameters.altAbility.fireTimeFactor = valueOrRandom(parameters.altAbility.fireTimeFactor, seed, "fireTimeFactor")
      parameters.altAbility.baseDpsFactor = valueOrRandom(parameters.altAbility.baseDpsFactor, seed, "baseDpsFactor")
      parameters.altAbility.energyUsageFactor = valueOrRandom(parameters.altAbility.energyUsageFactor, seed, "energyUsageFactor")
    
      config.altAbility.fireTime = scaleConfig(parameters.altAbility.fireTimeFactor, config.altAbility.fireTime)
      config.altAbility.baseDps = scaleConfig(parameters.altAbility.baseDpsFactor, config.altAbility.baseDps)
      config.altAbility.energyUsage = scaleConfig(parameters.altAbility.energyUsageFactor, config.altAbility.energyUsage) or 0
    
      -- preprocess melee alt attack config
      if config.altAbility.damageConfig and config.altAbility.damageConfig.knockbackRange then
        config.altAbility.damageConfig.knockback = scaleConfig(parameters.altAbility.fireTimeFactor, config.altAbility.damageConfig.knockbackRange)
      end
    
      -- preprocess ranged alt attack config
      if config.altAbility.projectileParameters then
        config.altAbility.projectileType = randomFromList(config.altAbility.projectileType, seed, "projectileType")
        config.altAbility.projectileCount = randomIntInRange(config.altAbility.projectileCount, seed, "projectileCount") or 1
        config.altAbility.fireType = randomFromList(config.altAbility.fireType, seed, "fireType") or "auto"
        config.altAbility.burstCount = randomIntInRange(config.altAbility.burstCount, seed, "burstCount")
        config.altAbility.burstTime = randomInRange(config.altAbility.burstTime, seed, "burstTime")
        if config.altAbility.projectileParameters.knockbackRange then
          config.altAbility.projectileParameters.knockback = scaleConfig(parameters.altAbility.fireTimeFactor, config.altAbility.projectileParameters.knockbackRange)
        end
      end
     
      tryThis_nonsense.vanilla_build(directory, config, parameters, level, seed)
    end
    basically just took a big chunk of code that processes the primary ability and did a replacement from primary to alt because I didn't see where the same processing occurred for the alt ability.

    If you can't hook that, then clone buildweapon.lua but name it something else, and point to that. Add that code to it though, minus the hook.
     
    Last edited: Jan 2, 2017
  5. IHart

    IHart Scruffy Nerf-Herder

    I'm sorry this is so confusing, I'm looking for a way to define the powerProjectileParameters for the primary ability
     
  6. IHart

    IHart Scruffy Nerf-Herder

    update, the builder only ADDS status effects to a projectile, it cannot overwrite or remove status effects assigned to the projectile in its file. made new projectile files for my uses.
     
  7. IHart

    IHart Scruffy Nerf-Herder

    can you describe to me how this hook works? i dont understand which phrases are key identifiers for the hook and which are arbitrary.

    edit: specifically within this portion "tryThis_nonsense = {vanilla_build = build}"
    what is this pointing to, and by extension what is needed to make sure it gets hooked in the correct context
     
  8. bk3k

    bk3k Oxygen Tank

    Since I misunderstood, that code isn't what you need

    Code:
    tryThis_nonsense = {vanilla_build = build}
    could be lengthened to this
    Code:
    tryThis_nonsense = { }
    tryThis_nonsense.vanilla_build = build
    
    As it where, functions can be copied just like tables. And without using () you can address them directly rather than run them(where you are addressing whatever they return). I'm copying the build function itself(rather that the output from running it) into tryThis_nonsense.vanilla_build

    Code:
    build = function()
    is identical to
    Code:
    function build()
    but I think that makes it more clear I'm reassigning the build namespace to a new function. In other words, I'm over-writting build thus why I copied it first.

    Then there is my code(which honestly I didn't test at all nor review since).
    Finally at the end
    Code:
    tryThis_nonsense.vanilla_build(directory, config, parameters, level, seed)
    That's running the copied aka probably original/vanilla build function and passing the variables back to it. Of course before that, the config and parameters tables have been modified by that code so the vanilla function is receiving modified information.

    In a nutshell, that's function hooking. It is like inserting a link into a chain.

    I didn't notice this before, but you can't do it that way because of this
    Code:
    "builder" : "/items/buildscripts/buildweapon.lua"
    That's rather unfortunate that they are expecting a string instead of
    Code:
    "builder" : [ "/items/buildscripts/buildweapon.lua" ]
    where in you could do this
    Code:
    "builder" : [ "/items/buildscripts/buildweapon.lua", "/somePath/buildweapon_hook.lua"]
    as multiple scripts can be evaluated in order.

    So the exclusive option would only be to use your own custom version of buildweapon.lua. That's rather unfortunate as it makes it more complex/less flexible to work with other mods that would also want to replace this script. Not to say you can't, but you'd need to work more directly with other modders that do this. Function hooking by contrast is far more compatible (when you have the option).
     
    IHart likes this.
  9. IHart

    IHart Scruffy Nerf-Herder

    I follow 99% now.
    is this in the proper order?
    "builder" : [ "/items/buildscripts/buildweapon.lua", "/somePath/buildweapon_hook.lua"]
    would you not want the hook to come first?
     
  10. bk3k

    bk3k Oxygen Tank

    You don't want the hook to come first because the hooks will overwrite the original namespace(after copying the contents). Such that.

    Code:
    function init()
    --do stuff
    end
    
    function update(dt)
      --do stuff
    end
    
    backup = {
      init = init,
      update = update
      }
    
    backup.init will at that point be identical to init
    backup.update will at that point be identical to update
    So compare to doing this


    Code:
    backup = {
      init = init,
      update = update
      }
    
    function init()
    --do stuff
    end
    
    function update(dt)
      --do stuff
    end
    
    backup.init will be nil
    backup.update will be nil
    because init and update namespaces where not yet assigned a value(functions are a LUA data type).

    Also you might assume that Init() is the first thing that will happen but that isn't true. Notice how no LUA code you've seen calls it? The engine calls that and some other namespaces when appropriate. Before that it will go over the code from top to bottom. Adding another script means it will go over that script too prior to init() occuring. It would go over them in order. And so to modify an existing environment, you'd want to come after the original.

    Here's something else to know. At that point, you won't have most of the environment you expect. For example sb.logInfo() will result in an error like
    'attempt to index nil value "sb" '
    That stuff will get added to the environment at a point after your scripts are evaluated but before(perhaps just before) the call to init()

    So you can't add to storage, self, etc yet. You'd need to do that in init() But you can add your own tables and store data in them.
     
    IHart likes this.

Share This Page