Jump to content

[TUTORIAL] Character Transformation


Recommended Posts

engine.luaThe following is a short tutorial on how to use the key handler component which is used by @rons0n, @Purswader and @SenL to make their characters transform into different character builds. The zip file in this post is to be extracted to the root directory of your mod, the location of your modmain.lua file.

After you have extracted the files there is a README file which contains the following information:

 

Spoiler

 

NOTE: IF YOU WERE USING THE KEYHANDLER COMPONENT BEFORE
PLEASE REMOVE ALL CODE ASSOCIATED WITH IT AND REPLACE IT WITH THE FOLLOWING.

ADD THE FOLLOWING TO THE MODMAIN.LUA FILE.

 

 

-- Import the engine.
modimport("engine.lua")

-- Imports to keep the keyhandler from working while typing in chat.
Load "chatinputscreen"
Load "consolescreen"
Load "textedit"

After you've added the above to your modmain.lua file the keyhandler component will load successfully, but it will not be attached to any character so we need to attach it to our character.

characterprefab.lua

local function common_postinit(inst)
	inst:AddComponent("keyhandler")
    inst.components.keyhandler:AddActionListener("[INSERT MOD RPC NAMESPACE HERE]", TUNING["[INSERT CHARACTER NAME AS UPPER CASE HERE]"].KEY, "[INSERT MOD RPC NAME HERE]", "[INSERT KEYUP, KEYDOWN OR nil]")
end

modmain.lua

--Transformation Section
local mod_option = "[INSERT MOD OPTION FOR KEY HERE]"
local character = "[INSERT CHARACTER NAME HERE]"
GLOBAL.TUNING[GLOBAL.string.upper(character)] = {}
GLOBAL.TUNING[GLOBAL.string.upper(character)].KEY = GetModConfigData(mod_option) or 122

local function transform(inst)

	-- Ignore while we are a ghost.
	if inst:HasTag("playerghost") then return end
	-- Ignore while we are busy.
	if inst.sg:HasStateTag("busy") then return end

	if not inst.transformed then
		-- Set the animation build for the transformed form here.
		-- Change the stats for the transformed character here.
	else
		-- Set the animation build for the original form here.
		-- Change the stats for the original character here.
	end

	inst.transformed = not inst.transformed

end

AddModRPCHandler("[INSERT MOD RPC NAMESPACE HERE]", "[INSERT MOD RPC NAME HERE]", transform)

modinfo.lua

configuration_options =
{
    {
        name = "[INSERT MOD OPTION HERE]",
        label = "Transformation Key",
        hover = "This is the key use to transform [INSERT CHARACTER NAME HERE].",
        options =
        {
            {description="TAB", data = 9},
            {description="KP_PERIOD", data = 266},
            {description="KP_DIVIDE", data = 267},
            {description="KP_MULTIPLY", data = 268},
            {description="KP_MINUS", data = 269},
            {description="KP_PLUS", data = 270},
            {description="KP_ENTER", data = 271},
            {description="KP_EQUALS", data = 272},
            {description="MINUS", data = 45},
            {description="EQUALS", data = 61},
            {description="SPACE", data = 32},
            {description="ENTER", data = 13},
            {description="ESCAPE", data = 27},
            {description="HOME", data = 278},
            {description="INSERT", data = 277},
            {description="DELETE", data = 127},
            {description="END", data   = 279},
            {description="PAUSE", data = 19},
            {description="PRINT", data = 316},
            {description="CAPSLOCK", data = 301},
            {description="SCROLLOCK", data = 302},
            {description="RSHIFT", data = 303}, -- use SHIFT instead
            {description="LSHIFT", data = 304}, -- use SHIFT instead
            {description="RCTRL", data = 305}, -- use CTRL instead
            {description="LCTRL", data = 306}, -- use CTRL instead
            {description="RALT", data = 307}, -- use ALT instead
            {description="LALT", data = 308}, -- use ALT instead
            {description="ALT", data = 400},
            {description="CTRL", data = 401},
            {description="SHIFT", data = 402},
            {description="BACKSPACE", data = 8},
            {description="PERIOD", data = 46},
            {description="SLASH", data = 47},
            {description="LEFTBRACKET", data     = 91},
            {description="BACKSLASH", data     = 92},
            {description="RIGHTBRACKET", data = 93},
            {description="TILDE", data = 96},
            {description="A", data = 97},
            {description="B", data = 98},
            {description="C", data = 99},
            {description="D", data = 100},
            {description="E", data = 101},
            {description="F", data = 102},
            {description="G", data = 103},
            {description="H", data = 104},
            {description="I", data = 105},
            {description="J", data = 106},
            {description="K", data = 107},
            {description="L", data = 108},
            {description="M", data = 109},
            {description="N", data = 110},
            {description="O", data = 111},
            {description="P", data = 112},
            {description="Q", data = 113},
            {description="R", data = 114},
            {description="S", data = 115},
            {description="T", data = 116},
            {description="U", data = 117},
            {description="V", data = 118},
            {description="W", data = 119},
            {description="X", data = 120},
            {description="Y", data = 121},
            {description="Z", data = 122},
            {description="F1", data = 282},
            {description="F2", data = 283},
            {description="F3", data = 284},
            {description="F4", data = 285},
            {description="F5", data = 286},
            {description="F6", data = 287},
            {description="F7", data = 288},
            {description="F8", data = 289},
            {description="F9", data = 290},
            {description="F10", data = 291},
            {description="F11", data = 292},
            {description="F12", data = 293},
 
            {description="UP", data = 273},
            {description="DOWN", data = 274},
            {description="RIGHT", data = 275},
            {description="LEFT", data = 276},
            {description="PAGEUP", data = 280},
            {description="PAGEDOWN", data = 281},
 
            {description="0", data = 48},
            {description="1", data = 49},
            {description="2", data = 50},
            {description="3", data = 51},
            {description="4", data = 52},
            {description="5", data = 53},
            {description="6", data = 54},
            {description="7", data = 55},
            {description="8", data = 56},
            {description="9", data = 57},
        },
        default = 122,
    },
 
} 

 

Now that we have all the files set up like they need to be let's update our code so it works properly. Simply modify the following lines with the appropriate setting and you're done. You now have the ability for your character to transform back and forth between forms.

inst.components.keyhandler:AddActionListener("[INSERT MOD RPC NAMESPACE HERE]", TUNING["[INSERT CHARACTER NAME AS UPPER CASE HERE]"].KEY, "[INSERT MOD RPC NAME HERE]", "[INSERT KEYUP, KEYDOWN OR nil]")
local mod_option = "[INSERT MOD OPTION FOR KEY HERE]"
local character = "[INSERT CHARACTER NAME HERE]"
AddModRPCHandler("[INSERT MOD RPC NAMESPACE HERE]", "[INSERT MOD RPC NAME HERE]", transform)
name = "[INSERT MOD OPTION HERE FOR KEY]",
hover = "This is the key use to transform [INSERT CHARACTER NAME HERE].",

 

If you have any questions, please post them below. Remember to give me credit (Kzisor/Ysovuka) if you use this keyhandler.

Regards,

Kzisor/Ysovuka

 

Keyhandler.zip

Edited by Kzisor
Fixed a crash on c_reset().
Link to comment
Share on other sites

Very nice tutorial. Thank you.

Can you also add (if not yet there) how to avoid triggering the transform when dialog box is open (such as naming a sign etc)?

This happens (or happened) on a mod "Puppy Princess" (art is extremely done by the way, I love it) where the result is that you can't label sign with certain keys. I think I saw the workaround in "Starve Wars" mod but not certain if it's the most effective way.

 

Edited by SenL
Link to comment
Share on other sites

@SenL are you specifically talking about the writable signs in Don't Starve Together? If so, I would probably need to create another file in the screens folder which prevented that as I am doing with Chat and console.

Edit:

I've updated the zip file to contain the information needed to prevent this from happening.

Edited by Kzisor
Link to comment
Share on other sites

So are below correct

1) put engine.lua in root of your mod (along with modinfo.lua and modmain.lua)

2) copy "core" folder (and all its contents) there too (along with anim folder, scripts folder, etc)

3) copy "keyhandler.lua" to scripts\components folder (overwrite old one)

4) add those modimport() and load() in modmain.lua (on top)

?

 

Questions:

ThePlayer:PushEvent( "gamepaused", true )

Pause in DST? or it's just an event called "gamepaused"?

Link to comment
Share on other sites

6 minutes ago, SenL said:

So are below correct

1) put engine.lua in root of your mod (along with modinfo.lua and modmain.lua)

2) copy "core" folder (and all its contents) there too (along with anim folder, scripts folder, etc)

3) copy "keyhandler.lua" to scripts\components folder (overwrite old one)

4) add those modimport() and load() in modmain.lua (on top)

?

If you're using the old keyhandler, make sure you overwrite the old keyhandler code.

5 minutes ago, SenL said:

ThePlayer:PushEvent( "gamepaused", true )

Pause in DST? or it's just an event called "gamepaused"?

That is simply an event which this mod uses in order to know whether the game should be paused or not. In DS when you open the console it would pause the game, that is why I named the event that.

Link to comment
Share on other sites

11 minutes ago, SenL said:

It works.

But ... something's different with console when you hit `

I don't see the debug prints anymore...

Thanks for the report, it seems that the console file was corrupted in the compression. Please download the .zip file and overwrite the files once more and it should work.

Link to comment
Share on other sites

Since the last game update or two, I can't see my debug prints anymore.

Is it related to the consolescreen.lua here? if not, how do I see prints now...

Edit:

Ok the prints are now in server_log.txt.

I do see "Error decoding lua RPC sender"

This happens at:

(modmain.lua)

...

local function GetModRPC(namespace, name)
    return MOD_RPC[namespace][name]
end

...

SendModRPCToServer(GetModRPC(modname, "somefunction"))

Edited by SenL
Link to comment
Share on other sites

15 minutes ago, SenL said:

Since the last game update or two, I can't see my debug prints anymore.

Is it related to the consolescreen.lua here? if not, how do I see prints now...

Edit:

Ok the prints are now in server_log.txt.

I do see "Error decoding lua RPC sender"

This happens at:

 

Hidden Content

 

Remove the GetModRPC function from your code, @PeterA added that function to the mod API in a recent update. If you're having an issue with the debug printouts, try downloading the files from the first post again and replacing them in your folder. I've made an update to them recently so I'm not sure if you have the most recent updated files.

Link to comment
Share on other sites

I removed the function but still getting "Error decoding lua RPC sender" and the code does not work anymore.

 

Spoiler

 

(modmain.lua)

local function ExpOnDig(inst)

...

end

AddModRPCHandler(modname, "DigExp", ExpOnDig)
local old_Dig = GLOBAL.ACTIONS.DIG.fn
GLOBAL.ACTIONS.DIG.fn = function(act)
    if act.doer:HasTag("gggroot") then
        if not act.doer.transformed then
            SendModRPCToServer(GetModRPC(modname, "DigExp"))
        end
    end    
    return old_Dig(act)
end

 

Edit:

Fixed by not using modname variable. I use literal string "Groot" on both AddModRPCHandler and SendModRPCToServer for the namespace.

Should I be using modname variable on both places?

Edited by SenL
Link to comment
Share on other sites

I am trying to use your code to implement a meteor cast when hit on a key.

Example: I press 'V' and a

_G.c_remote('c_spawn(\"shadowmeteor\")')

How do I do that? It is meant for rude people on my server. So only client side mod and you ve to be admin. No RPC calls is needed.

 

Its really difficult, when it comes to disable these functionality, when you are chatting or using the console

Link to comment
Share on other sites

@Kzisor Is it possible to expand the keylistener component to allow for "charging" transformations? I suppose listening for key_up does the trick, but I don't want to write a part of the code outside of the component, and changing the file would require a high priority due to overriding, which is something I'd rather avoid.

Edited by Mobbstar
Mentioned
Link to comment
Share on other sites

10 hours ago, werlpolf said:

I am trying to use your code to implement a meteor cast when hit on a key.

Example: I press 'V' and a


_G.c_remote('c_spawn(\"shadowmeteor\")')

How do I do that? It is meant for rude people on my server. So only client side mod and you ve to be admin. No RPC calls is needed.

 

Its really difficult, when it comes to disable these functionality, when you are chatting or using the console

If you are using my code in complete and not simply the keyhandler component it's already taken into account.

8 hours ago, Mobbstar said:

@Kzisor Is it possible to expand the keylistener component to allow for "charging" transformations? I suppose listening for key_up does the trick, but I don't want to write a part of the code outside of the component, and changing the file would require a high priority due to overriding, which is something I'd rather avoid.

This would not be very difficult, give me some time to implement it into the current design.

The .zip file now contains this ability. You need 2 ModRPC functions, the first would be to set the server to "charging" the ability. The second would be to "release" the charge and do the actual ability.

Example (common_postinit):

inst.components.keyhandler:AddActionListener("[INSERT MOD RPC NAMESPACE HERE]", TUNING["[INSERT CHARACTER NAME AS UPPER CASE HERE]"].KEY, "ChargeAbility", "KEYDOWN")

inst.components.keyhandler:AddActionListener("[INSERT MOD RPC NAMESPACE HERE]", TUNING["[INSERT CHARACTER NAME AS UPPER CASE HERE]"].KEY, "ReleaseAbility", "KEYUP")

 

Edited by Kzisor
Link to comment
Share on other sites

Just now, Mobbstar said:

@Kzisor I didn't expect you to be that fast. Thank you very much!

This opens up so many new possibilities. Charged Attacks, Alternative Vision, Stealth Mode, Pseudo-Passive Abilities as a whole!

Due to the nature in which I set up the actual component it was a very simple change.

Note: If you leave the "Event" parameter of the "AddActionListener" nil it will default to "keyup" behavior. This is to prevent any issue with other mods which use this particular component.

Link to comment
Share on other sites

I get a crash when I press ` (to open console) when on character select screen (any, even on Wilson).

Error is something like:

attempt to index global 'ThePlayer' (a nil value)

consolescreen.lua:5 in (method) OnBecomeActive (Lua) <3-9>

Nothing major I guess. Who would open console when in character select screen?

Link to comment
Share on other sites

2 minutes ago, SenL said:

I get a crash when I press ` (to open console) when on character select screen (any, even on Wilson).

Nothing major I guess. Who would open console when in character select screen?

Thanks, that has been fixed.

Link to comment
Share on other sites

local function common_postinit(inst)
	inst:AddComponent("keyhandler")
    inst.components.keyhandler:AddActionListener("[INSERT MOD RPC NAMESPACE HERE]", TUNING["[INSERT CHARACTER NAME AS UPPER CASE HERE]"].KEY, "[INSERT MOD RPC NAME HERE]", "[INSERT KEYUP, KEYDOWN OR nil]")
end

 Give ME Example !!

Edited by Kyouyashinji
Link to comment
Share on other sites

6 hours ago, Kyouyashinji said:

local function common_postinit(inst)
	inst:AddComponent("keyhandler")
    inst.components.keyhandler:AddActionListener("[INSERT MOD RPC NAMESPACE HERE]", TUNING["[INSERT CHARACTER NAME AS UPPER CASE HERE]"].KEY, "[INSERT MOD RPC NAME HERE]", "[INSERT KEYUP, KEYDOWN OR nil]")
end

 Give ME Example !!

See mods Broccoli, Groot, Luffy, Musha or Tamamo.

Link to comment
Share on other sites

Is it possible to use this keyhandler for a timed stealth mode with cooldown?

Basically, all monsters (even bosses), pigs/bunnies/merms/etc and animals will ignore the stealthing character no matter what they/he/she/it does. (including attacking the enemy. Any aggro character already has on before stealth is nullified on activation.) Said character's still vulnerable to attacks while stealthed, though.

Effect lasts for around 10 seconds with a 3 minute cooldown. 

Can the Keyhandler do this, by any chance? 

My knowledge of modding right now is basically item, projectile and character creation basics right now, so I don't know the tags and the placements needed to do more complex stuff like level systems and said stealth system. Sorry if I don't understand everything right off the bat.

 

Link to comment
Share on other sites

23 minutes ago, KyoruKyoruMii said:

Is it possible to use this keyhandler for a timed stealth mode with cooldown?

Basically, all monsters (even bosses), pigs/bunnies/merms/etc and animals will ignore the stealthing character no matter what they/he/she/it does. (including attacking the enemy. Any aggro character already has on before stealth is nullified on activation.) Said character's still vulnerable to attacks while stealthed, though.

Effect lasts for around 10 seconds with a 3 minute cooldown. 

Can the Keyhandler do this, by any chance? 

My knowledge of modding right now is basically item, projectile and character creation basics right now, so I don't know the tags and the placements needed to do more complex stuff like level systems and said stealth system. Sorry if I don't understand everything right off the bat.

 

afaik, all the keyhandler does is listen for key presses, so if you want to activate stealth via key, it can be done, but ofc the stealth code itself you gotta manage as its obviously not part of the keyhandler

Link to comment
Share on other sites

Hey @Kzisor do you think you add a RemoveActionListener function?

As I'd like to use this for an equipped item, and it seems the ignore feature would ignore all keys, making it not too compatible with other mods that use this component, and could effect their characters.

Unless there is a better way that slipped from my mind, if so let me know. Thanks.

 

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...