Jump to content

Modifying the Tornado Prefab and Stategraph


Recommended Posts

Hello,

So I've finally figured out how to use AddPrefabPostInit correctly (mostly), but I'm having an issue when I attempt to make changes to the Tornado Stategraph. I'm just trying to make the tornado ignore structures and some plants when destroying stuff.

I'm literally copy pasting the relevant function, and adding a few tags to the CANTTAG parameter on line 3. My code is as follows:

local function destroystuff(inst)
	local x, y, z = inst.Transform:GetWorldPosition()
	local ents = TheSim:FindEntities(x, y, z, 3, nil, { "INLIMBO" ,"structure","witherable"}, TARGET_TAGS)
	for i, v in ipairs(ents) do
		--stuff might become invalid as we work or damage during iteration
		if v ~= inst.WINDSTAFF_CASTER and v:IsValid() then
			if v.components.health ~= nil and
				not v.components.health:IsDead() and
				v.components.combat ~= nil and
				v.components.combat:CanBeAttacked() and
				(TheNet:GetPVPEnabled() or not (inst.WINDSTAFF_CASTER_ISPLAYER and v:HasTag("player"))) then
				local damage =
					inst.WINDSTAFF_CASTER_ISPLAYER and
					v:HasTag("player") and
					TUNING.TORNADO_DAMAGE * TUNING.PVP_DAMAGE_MOD or
					TUNING.TORNADO_DAMAGE
				v.components.combat:GetAttacked(inst, damage, nil, "wind")
				if v:IsValid() and
					inst.WINDSTAFF_CASTER ~= nil and inst.WINDSTAFF_CASTER:IsValid() and
					v.components.combat ~= nil and
					not (v.components.health ~= nil and v.components.health:IsDead()) and
					not (v.components.follower ~= nil and
						v.components.follower.keepleaderonattacked and
						v.components.follower:GetLeader() == inst.WINDSTAFF_CASTER) then
					v.components.combat:SuggestTarget(inst.WINDSTAFF_CASTER)
				end
			elseif v.components.workable ~= nil and
				v.components.workable:CanBeWorked() and
				v.components.workable:GetWorkAction() and
				WORK_ACTIONS[v.components.workable:GetWorkAction().id] then
				SpawnPrefab("collapse_small").Transform:SetPosition(v.Transform:GetWorldPosition())
				v.components.workable:WorkedBy(inst, 2)
				--v.components.workable:Destroy(inst)
			end
		end
	end
	print("Tornado SG updated")
end
	
AddStateGraphPostInit("SGtornado", function(inst)
	inst.destroystuff = destroystuff
end)

And my error log is:

[00:00:55]: [string "../mods/BlarghTestClean/modmain.lua"]:246: attempt to call global 'AddStateGraphPostInit' (a nil value)
LUA ERROR stack traceback:
        ../mods/BlarghTestClean/modmain.lua(246,1) in main chunk
        =[C] in function 'xpcall'
        scripts/util.lua(711,1) in function 'RunInEnvironment'
        scripts/mods.lua(513,1) in function 'InitializeModMain'
        scripts/mods.lua(487,1) in function 'LoadMods'
        scripts/main.lua(302,1) in function 'ModSafeStartup'
        scripts/main.lua(375,1)
        =[C] in function 'SetPersistentString'
        scripts/mainfunctions.lua(26,1) in function 'SavePersistentString'
        scripts/modindex.lua(80,1)
        =[C] in function 'GetPersistentString'
        scripts/modindex.lua(67,1) in function 'BeginStartupSequence'
        scripts/main.lua(374,1) in function 'callback'
        scripts/modindex.lua(545,1)
        =[C] in function 'GetPersistentString'
        scripts/modindex.lua(519,1) in function 'Load'
        scripts/main.lua(373,1) in main chunk
[00:00:55]: [string "scripts/mainfunctions.lua"]:1079: variable 'global_error_widget' is not declared
LUA ERROR stack traceback:
        =[C] in function 'error'
        scripts/strict.lua(23,1)
        scripts/mainfunctions.lua(1079,1)
        =[C] in function 'GetPersistentString'
        scripts/quagmire_recipebook.lua(54,1) in function 'Load'
        scripts/main.lua(320,1) in function 'ModSafeStartup'
        scripts/main.lua(375,1)
        =[C] in function 'SetPersistentString'
        scripts/mainfunctions.lua(26,1) in function 'SavePersistentString'
        scripts/modindex.lua(80,1)
        =[C] in function 'GetPersistentString'
        scripts/modindex.lua(67,1) in function 'BeginStartupSequence'
        scripts/main.lua(374,1) in function 'callback'
        scripts/modindex.lua(545,1)
        =[C] in function 'GetPersistentString'
        scripts/modindex.lua(519,1) in function 'Load'
        scripts/main.lua(373,1) in main chunk

I'm guessing this is because the Tornado doesn't exist when the AddStategraphPostInit is being run? Or maybe my method of making the modification is just wrong.

Any advice would be appreciated. Thanks!

Link to comment
Share on other sites

Heh, the hint it gave me turned out to be a bit misleading! It didn't feel like a scope issue, it was actually a case issue. The G in AddStategraphPostInit shouldn't have been capitalized, and it worked without the GLOBAL.

Well, it didn't actually work, it just didn't give me an error. So the mod runs, but it doesn't seem to be doing anything. I'm guessing i'm not calling the function correctly, because the "Tornado SG updated" line isn't getting printed to console. 

Looks like i haven't quite figured out the Add__GraphPostInit functions yet! I actually wound up trying it another way as well, but that ended up with the same results. No errors, but not working as intended.

Anyone mind taking a second glance? Thanks!

Link to comment
Share on other sites

Well, you're just setting the variable destroystuff on the inst, to be the function. That won't do anything.

Instead, do this:

AddStategraphPostInit("SGtornado", destroystuff)

You're adding your function to the list/table of (postinit) functions for it to run on the given stategraph, after constructing the stategraph. In this case the "SGTornado" stategraph. That's all :)

EDIT:
Ehm...wait...I think I'm missing something. What you did SHOULD work. Let me just look at the game code for a minute.

Got it. Give me a minute to do a write-up.

Where are you looking for the print() to show up?

Edited by Ultroman
Link to comment
Share on other sites

The print() will only show up in the server_log (or is it the client_log?), and not the in-game console. What spell are you talking about?

Try this very intrusive code, to see if what you expect happens.

local function destroystuffyay(inst)
	local x, y, z = inst.Transform:GetWorldPosition()
	local ents = TheSim:FindEntities(x, y, z, 3, nil, { "INLIMBO" ,"structure","witherable"}, TARGET_TAGS)
	for i, v in ipairs(ents) do
		--stuff might become invalid as we work or damage during iteration
		if v ~= inst.WINDSTAFF_CASTER and v:IsValid() then
			if v.components.health ~= nil and
				not v.components.health:IsDead() and
				v.components.combat ~= nil and
				v.components.combat:CanBeAttacked() and
				(TheNet:GetPVPEnabled() or not (inst.WINDSTAFF_CASTER_ISPLAYER and v:HasTag("player"))) then
				local damage =
					inst.WINDSTAFF_CASTER_ISPLAYER and
					v:HasTag("player") and
					TUNING.TORNADO_DAMAGE * TUNING.PVP_DAMAGE_MOD or
					TUNING.TORNADO_DAMAGE
				v.components.combat:GetAttacked(inst, damage, nil, "wind")
				if v:IsValid() and
					inst.WINDSTAFF_CASTER ~= nil and inst.WINDSTAFF_CASTER:IsValid() and
					v.components.combat ~= nil and
					not (v.components.health ~= nil and v.components.health:IsDead()) and
					not (v.components.follower ~= nil and
						v.components.follower.keepleaderonattacked and
						v.components.follower:GetLeader() == inst.WINDSTAFF_CASTER) then
					v.components.combat:SuggestTarget(inst.WINDSTAFF_CASTER)
				end
			elseif v.components.workable ~= nil and
				v.components.workable:CanBeWorked() and
				v.components.workable:GetWorkAction() and
				WORK_ACTIONS[v.components.workable:GetWorkAction().id] then
				SpawnPrefab("collapse_small").Transform:SetPosition(v.Transform:GetWorldPosition())
				v.components.workable:WorkedBy(inst, 2)
				--v.components.workable:Destroy(inst)
			end
		end
	end
	print("Tornado SG updated")
end

local function alterSGTornado(sg)
	sg.states["idle"].onenter = function(inst)
		inst.Physics:Stop()
		inst.AnimState:PushAnimation("tornado_loop", false)
		destroystuffyay(inst)
	end
	sg.states["walk"].onenter = function(inst)
		inst.components.locomotor:WalkForward()
		inst.AnimState:PushAnimation("tornado_loop", false)
		destroystuffyay(inst)
	end
	sg.states["walk"].timeline =
	{
		TimeEvent(5*FRAMES, destroystuffyay),
	}
	sg.states["run_start"].timeline =
	{
		TimeEvent(5*FRAMES, destroystuffyay),
	}
	sg.states["run"].timeline =
	{
		TimeEvent(5*FRAMES, destroystuffyay),
	}
	sg.states["run_stop"].timeline =
	{
		TimeEvent(5*FRAMES, destroystuffyay),
	}
end

AddStategraphPostInit("SGtornado", alterSGTornado)

 

As I said, this is very intrusive, in that it directly overrides those state functions and arrays. It has to, in order to properly change them. It REALLY would be best, if what you had originally (after the capital G) just worked. It should...I think. Unless it "saves" the destroystuff function to the inside of the state functions, and doesn't just read/use it from the local function (which you correctly changed).

Edited by Ultroman
Link to comment
Share on other sites

Hmm, that didn't seem to work, neither the Print() is appearing, nor is the tornado ignoring the things it should be ignoring. But I believe I follow what you're saying. I'll try modifying the individual Stategraphs for Idle and Walk (and maybe even "run_start", "run", and "run_stop". It looks like the function gets called there too), with AddStategraphState. 

I'm really just glad that my initial approach was correct (in theory and minus a few typos :p).

Thanks again.

Link to comment
Share on other sites

If you follow the modding API, this is supposed to work. This snippet is from the modding API documentation.

local newIdleTimeout = function(inst)
	if math.random() < 0.5 then
		inst.sg:GoToState("funnyidle")
	else
		inst.sg:GoToState("spin_around")
	end
end

local function SGWilsonPostInit(sg)
	-- note! This overwrites the old timeout behavior! If possible you should
	-- always try appending your behaviour instead.
	sg.states["idle"].ontimeout = newIdleTimeout
end

AddStategraphPostInit("wilson", SGWilsonPostInit)

 

What are you using to test the tornado? Because just spawning a tornado is a different prefab (fans.lua) than the one spawned by the Windstaff (staff_tornado.lua). The one from the Windstaff uses the stategraph, and the one you spawn when doing c_spawn("tornado") does not.

Well, apparently that's not entirely right, either. Man, they've really messed up the naming of these things...

Link to comment
Share on other sites

This works. We're not supposed to alter "SGtornado" stategraph, but "tornado" stategraph. I also needed to add GLOBAL to a bunch of things, and copy the WORK_ACTIONS and TARGET_TAGS stuff, but it works.

local WORK_ACTIONS =
{
    CHOP = true,
    DIG = true,
    HAMMER = true,
    MINE = true,
}

local TARGET_TAGS = { "_combat" }
for k, v in pairs(WORK_ACTIONS) do
    table.insert(TARGET_TAGS, k.."_workable")
end

local function destroystuff(inst)
	local x, y, z = inst.Transform:GetWorldPosition()
	local ents = TheSim:FindEntities(x, y, z, 3, nil, { "INLIMBO" ,"structure","witherable"}, TARGET_TAGS)
	for i, v in ipairs(ents) do
		--stuff might become invalid as we work or damage during iteration
		if v ~= inst.WINDSTAFF_CASTER and v:IsValid() then
			if v.components.health ~= nil and
				not v.components.health:IsDead() and
				v.components.combat ~= nil and
				v.components.combat:CanBeAttacked() and
				(GLOBAL.TheNet:GetPVPEnabled() or not (inst.WINDSTAFF_CASTER_ISPLAYER and v:HasTag("player"))) then
				local damage =
					inst.WINDSTAFF_CASTER_ISPLAYER and
					v:HasTag("player") and
					GLOBAL.TUNING.TORNADO_DAMAGE * GLOBAL.TUNING.PVP_DAMAGE_MOD or
					GLOBAL.TUNING.TORNADO_DAMAGE
				v.components.combat:GetAttacked(inst, damage, nil, "wind")
				if v:IsValid() and
					inst.WINDSTAFF_CASTER ~= nil and inst.WINDSTAFF_CASTER:IsValid() and
					v.components.combat ~= nil and
					not (v.components.health ~= nil and v.components.health:IsDead()) and
					not (v.components.follower ~= nil and
						v.components.follower.keepleaderonattacked and
						v.components.follower:GetLeader() == inst.WINDSTAFF_CASTER) then
					v.components.combat:SuggestTarget(inst.WINDSTAFF_CASTER)
				end
			elseif v.components.workable ~= nil and
				v.components.workable:CanBeWorked() and
				v.components.workable:GetWorkAction() and
				WORK_ACTIONS[v.components.workable:GetWorkAction().id] then
				GLOBAL.SpawnPrefab("collapse_small").Transform:SetPosition(v.Transform:GetWorldPosition())
				v.components.workable:WorkedBy(inst, 2)
				--v.components.workable:Destroy(inst)
			end
		end
	end
	print("Tornado SG updated")
end

local function alterSGTornado(sg)
	sg.states["idle"].onenter = function(inst)
		inst.Physics:Stop()
		inst.AnimState:PushAnimation("tornado_loop", false)
		destroystuff(inst)
	end
	sg.states["walk"].onenter = function(inst)
		inst.components.locomotor:WalkForward()
		inst.AnimState:PushAnimation("tornado_loop", false)
		destroystuff(inst)
	end
	sg.states["walk"].timeline =
	{
		GLOBAL.TimeEvent(5*GLOBAL.FRAMES, destroystuff),
	}
	sg.states["run_start"].timeline =
	{
		GLOBAL.TimeEvent(5*GLOBAL.FRAMES, destroystuff),
	}
	sg.states["run"].timeline =
	{
		GLOBAL.TimeEvent(5*GLOBAL.FRAMES, destroystuff),
	}
	sg.states["run_stop"].timeline =
	{
		GLOBAL.TimeEvent(5*GLOBAL.FRAMES, destroystuff),
	}
end

AddStategraphPostInit("tornado", alterSGTornado)

 

EDIT: Just edited to add GLOBAL to the TUNING variables.

Edited by Ultroman
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
  • Create New...