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!
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.
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
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.
I'm sorry this is so confusing, I'm looking for a way to define the powerProjectileParameters for the primary ability
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.
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
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).
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?
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.