Jump to content

Example/template of character mod with level system?


Recommended Posts

Hi,

Does anyone know/have an example or template of character mod with level system (level up by killing, eat, etc)?

(I mean one with really good coding practice)

Thanks.

Edited by SenL
Link to comment
Share on other sites

I use the attached components as my leveling systems in all my characters. It's a generic leveling system which allows you to level up based on whatever criteria you want to do it by.

Example Usage:

Tuning Settings:

LEVEL_SYSTEM = "[INSERT NAME HERE]"
START_EXPERIENCE = 10 -- The amount of "experience" you need to level.
EXPERIENCE_MULT_MAX = 0.75 -- The multiplier in which will be used to make leveling easier/more difficult.
MAX_LEVEL = 1000 -- Any number really.

-- Experience on doing stuff.
FOOD_EXPERIENCE = 10
KILL_EXPERIENCE = 10

-- Stats MIN/MAX
MIN_HEALTH = 100
MAX_HEALTH = 400
MIN_HUNGER = 100
MAX_HUNGER = 600
MIN_SANITY = 100
MAX_SANITY = 200
MIN_DAMAGE_MULT = 1
MAX_DAMAGE_MULT = 5
MIN_SPEED_MULT = 1
MAX_SPEED_MULT = 3

Player Prefab:

local function determinestats( inst )

	-- Determine the new stat increases below.

	-- Determines the new max hunger.
	inst.components.hunger.max = math.ceil( MIN_HUNGER + inst.components.leveler:GetLevel( LEVEL_SYSTEM ) * (MAX_HUNGER - MIN_HUNGER) / MAX_LEVEL )

	-- Determins the new max health.
    inst.components.health.maxhealth = math.ceil( MIN_HEALTH + inst.components.leveler:GetLevel( LEVEL_SYSTEM ) * (MAX_HEALTH - MIN_HEALTH) / MAX_LEVEL )

	-- Determines the new max sanity.
    inst.components.sanity.max = math.ceil( MIN_SANITY + inst.components.leveler:GetLevel( LEVEL_SYSTEM ) * (MAX_SANITY - MIN_SANITY) / MAX_LEVEL )

	-- Determines the new combat modifier.
    inst.components.combat.damagemultiplier = ( MIN_DAMAGE_MULT + ( inst.components.leveler:GetLevel( LEVEL_SYSTEM ) * ( MAX_DAMAGE_MULT - MIN_DAMAGE_MULT ) / MAX_LEVEL ) )

	-- Determines the new run speed.
	local speed = ( MIN_SPEED_MULT + ( inst.components.leveler:GetLevel( LEVEL_SYSTEM )  * ( MAX_SPEED_MULT - MIN_SPEED_MULT )  / MAX_LEVEL ) )
    inst.components.locomotor:SetExternalSpeedMultiplier( inst, "Acceleration", speed )

	-- End new stat stuff.

	-- Makes the badges flash.
    inst.components.hunger:DoDelta(0)
    inst.components.health:DoDelta(0)
    inst.components.sanity:DoDelta(0)

end

local function onlevelup( inst, data )

	if data.system == LEVEL_SYSTEM then

		if inst.components.leveler:GetLevel( LEVEL_SYSTEM ) < MAX_LEVEL then
        
			inst.components.leveler:SetLevel( LEVEL_SYSTEM, ( inst.components.leveler:GetLevel( LEVEL_SYSTEM ) + 1 ) )
			local max_exp = ( ( inst.components.leveler:GetLevel( LEVEL_SYSTEM ) * START_EXPERIENCE ) * EXPERIENCE_MULT_MAX )
                                                                       
			inst.components.leveler:SetExperience( LEVEL_SYSTEM, data.experience ) -- Needs to be adjusted based on current experience over current level.
			inst.components.leveler:SetMaxExperience( LEVEL_SYSTEM, max_exp ) -- Sets the max experience needed for the next level.
                                                                       
			determinestats( inst ) -- Will determine the stat increases based on leveling.
                                                                   
			-- Sets the new max values here, allows you to use determine stats in other places without setting max health every time.
			inst.components.health:DoDelta( inst.components.health.maxhealth )
		    inst.components.hunger:DoDelta( inst.components.hunger.max )
		    inst.components.sanity:DoDelta( inst.components.sanity.max )
		end

	end

end
                                                                       
local function onleveldelta( inst, data )

	if data.system == LEVEL_SYSTEM then
                                                                       
		inst.components.leveler:SetExperience( LEVEL_SYSTEM, data.experience)

	end

end

local function reset_leveler( inst )
	-- Resets the leveler component with the system to 0.

	inst.components.leveler:SetLevel( LEVEL_SYSTEM, 0 )
	inst.components.leveler:SetExperience( LEVEL_SYSTEM, 0 )
	inst.components.leveler:SetMaxExperience( LEVEL_SYSTEM, START_EXPERIENCE )

end

local function ondeath( inst )

	reset_leveler()

end

local function oneat( inst, food )
	
	inst.components.leveler:DoDelta( LEVEL_SYSTEM, FOOD_EXPERIENCE )

end

local function onkilled( inst, data )

	inst.components.leveler:DoDelta( LEVEL_SYSTEM , KILL_EXPERIENCE )

end

local function OnExperienceDirty( inst, system )

	if inst ~= nil then

		local experience = inst[system.."_currentexperience"]:value()
		local maxexperience = inst[system.."_maxexperience"]:value()
		local level = inst[system.."_currentlevel"]:value()
		local maxlevel = inst[system.."_maxlevel"]:value()

		inst:PushEvent( "experiencedirty", { newpercent = experience / maxexperience,
			maxexperience = maxexperience,
			currentlevel = level,
			maxlevel = maxlevel } )

	end
end

local function AddLevelSystem( inst, system )

	inst[system.."_currentexperience"] = net_ushortint( inst.GUID, "leveler.SYSTEMS["..system.."].currentexperience", system.."_currentexperiencedirty")
	inst[system.."_maxexperience"] = net_ushortint( inst.GUID, "leveler.SYSTEMS["..system.."].maxexperience", system.."_currentexperiencedirty")
	inst[system.."_currentlevel"] = net_ushortint( inst.GUID, "leveler.SYSTEMS["..system.."].currentlevel", system.."_currentexperiencedirty")
	inst[system.."_maxlevel"] = net_ushortint( inst.GUID, "leveler.SYSTEMS["..system.."].maxlevel", system.."_currentexperiencedirty")

	inst:ListenForEvent(system.."_currentexperiencedirty", function( inst ) OnExperienceDirty(inst, system ) end)

end
                                                                       
local function onload( inst, data )
   
	-- NEED TO DETERMINE THE STATS ON LOAD!
	determinestats( inst )
                                                                       
	--re-set these from the save data, because of load-order clipping issues
    if data.health and data.health.health then inst.components.health:SetCurrentHealth(data.health.health) end
    if data.hunger and data.hunger.hunger then inst.components.hunger.current = data.hunger.hunger end
    if data.sanity and data.sanity.current then inst.components.sanity.current = data.sanity.current end

    inst.components.health:DoDelta(0)
    inst.components.hunger:DoDelta(0)
    inst.components.sanity:DoDelta(0)
                                                                       
end

local function commonpostinit(inst)

	AddLeveLSystem( inst, LEVEL_SYSTEM ) -- In commonpostinit so we get the value on the client as well as the server, mainly used for badges/widgets.

end

local function masterpostinit(inst)

	-- Because other things may use this component we ensure only to add it to our character once.
	if not inst.components.leveler then		
		inst:AddComponent( "leveler" )
	end

	-- Component set-up.
	inst.components.leveler:AddLevelSystem( LEVEL_SYSTEM )
	inst.components.leveler:SetMaxLevel( LEVEL_SYSTEM, MAX_LEVEL ) -- Note: Requires the level system because it's a generic component.
	inst.components.leveler:SetMaxExperience( LEVEL_SYSTEM, START_EXPERIENCE) -- Note: Requires the level system because it's a generic component.
	inst:ListenForEvent( "levelup", onlevelup )
	inst:ListenForEvent( "leveldelta" onleveldelta )
	inst:ListenForEvent( "death", ondeath )
	
	-- Gain Experience Events/Functions
	inst:ListenForEvent( "killed", onkilled ) --Used to gain experience on kill.
	inst.components.eater:SetOnEatFn( oneat )
                                                          
	-- Forces badge updates.
	inst:DoTaskInTime(1, function( inst ) 
		inst.components.leveler:DoDelta( LEVEL_SYSTEM, 0 )
	end)

	-- Needed to get the correct stat values based on level.
	inst.OnLoad = onload

end

modmain.lua

AddReplicableComponent("leveler")

 

The amount of code needed just for a "basic" leveling system probably is about 3/4 of this, however, these components allow you to have a ONE-MANY system, ONE character with MANY leveling systems. This is good because you it allows you to have different leveling systems to level different stats if you set it up that way.

leveler.lua

leveler_replica.lua

 

P.S. Remember to give credit where credit is due if you use these components.

Edited by Kzisor
Link to comment
Share on other sites

I have a question on this line:

inst.components.leveler:SetMaxExperience( LEVEL_SYSTEM, START_EXPERIENCE)

Why is the max xp is 10?

 

Edit:

Oh ok nevermind, in your system you reset exp to 0 as you level up and you need 10 to level up.

So it's not cumulative. Ok.

Edited by SenL
Link to comment
Share on other sites

15 hours ago, SenL said:

I have a question on this line:


inst.components.leveler:SetMaxExperience( LEVEL_SYSTEM, START_EXPERIENCE)

Why is the max xp is 10?

 

Edit:

Oh ok nevermind, in your system you reset exp to 0 as you level up and you need 10 to level up.

So it's not cumulative. Ok.

It can be any number, this was just a simple template of how the components work. You can modify any of the code to work the way you want it to work.

Example, in the onlevelup function you should see where it's actually setting the new MaxExperience for that level.

local max_exp = ( ( inst.components.leveler:GetLevel( LEVEL_SYSTEM ) * START_EXPERIENCE ) * EXPERIENCE_MULT_MAX )
                                                                       
inst.components.leveler:SetExperience( LEVEL_SYSTEM, data.experience ) -- Needs to be adjusted based on current experience over current level.
inst.components.leveler:SetMaxExperience( LEVEL_SYSTEM, max_exp ) -- Sets the max experience needed for the next level.

Level 1: 1 * 10 * .75 = 7.5

Level 2: 2 * 10 * .75 = 15

If you want to change how much max experience you need per level you simply adjust the equation for the max_exp variable.

 

Link to comment
Share on other sites

32 minutes ago, SenL said:

What if I only have onpreload (and no onload)?

onpreload means that it will be executed before the game is loaded. onload means it will be executed after the game has loaded.

You want to use onload function with these components as the game should be loaded when you get the values.

Edited by Kzisor
Elaborated on the answer.
Link to comment
Share on other sites

Just now, SenL said:

How come there is no onsave?

Character prefabs do not save component information, they only load it. The leveler component has it's own onsave and onload functions which will execute to load and save all the data properly. All you need to do is set up the character prefab based on what the component loaded.

Link to comment
Share on other sites

Nice.

More questions :).

Who do I give credits to? You?

Can you add (or is there a) GetNextLevelExp function?
I want the character to say this as they gain experience:

"Level 1: 60%"

(means I'm level 1 and 60% to next level)

Link to comment
Share on other sites

1 hour ago, SenL said:

Nice.

More questions :).

Who do I give credits to? You?

Can you add (or is there a) GetNextLevelExp function?
I want the character to say this as they gain experience:

"Level 1: 60%"

(means I'm level 1 and 60% to next level)

Yes credits go to me as I created those components.

You don't need a separate function to make that happen simply add the following code to the onleveldelta function.

Example:

local exp = inst.components.leveler:GetExperience( LEVEL_SYSTEM )
local max_exp = inst.components.leveler:GetMaxExperience( LEVEL_SYSTEM )

if max_exp > exp then
	local percent = (exp / max_exp) * 10
	inst.components.talker:Say( "Level: " .. tostring(inst.components.leveler:GetLevel( LEVEL_SYSTEM ) ) .. " " .. tostring( percent ) .. "%")
end

 

Edited by Kzisor
Code fix 2.0
Link to comment
Share on other sites

Ok great.

Right now I got a crash (only 1 mod active, my character mod). New game. Crashes when choosing this character

 

Spoiler

 

local function determinestats(inst)
  local currlevel = inst.components.leveler:GetLevel(LEVEL_SYSTEM) --crash here

  ...

 

 

leveler.lua:87: attempt to index field '?' (a nil value)

What am I missing...

 

Edit:

I think I found it. I call a function which calls determinestats() before this

inst.components.leveler:AddLevelSystem(LEVEL_SYSTEM)

in the master_postinit()

Duh

Edited by SenL
Link to comment
Share on other sites

4 minutes ago, SenL said:

Ok great.

Right now I got a crash (only 1 mod active, my character mod). New game. Crashes when choosing this character

 

Hidden Content

 

leveler.lua:87: attempt to index field '?' (a nil value)

What am I missing...

Make sure LEVEL_SYSTEM is not nil; that is about the only reason why it would be giving that error.

Link to comment
Share on other sites

I got it fixed. I had to move these much higher in the master_postinit

inst.components.leveler:AddLevelSystem(LEVEL_SYSTEM)
    inst.components.leveler:SetMaxLevel(LEVEL_SYSTEM, MAX_LEVEL)
    inst.components.leveler:SetMaxExperience(LEVEL_SYSTEM, START_EXPERIENCE)

No more crash.

However, I'm not seeing level up. I may need to put debug print outs...

Link to comment
Share on other sites

I got an issue with onkilled(inst, data)

I want to give exp based on victim's health and damage (some kind of arbitrary formula), not just 10 for all victims.

So I do:

local function onkilled(inst, data)

  if data.inst.components.health ~= nil then

  --if victim has health component, do stuff

  ...

  end

end

...

--in master_postinit

...

inst:ListenForEvent("killed", onkilled)

...

However it crashes on line "if data.inst.components.health ~= nil then"

data.inst is nil, it says.

When I changed it to

...

inst:ListenForEvent("entity_death", function(world, data) onkilled(inst, data) end, TheWorld)

...

It does not crash. So how do I get the original "killed" listenforevent to identify victim prefab (with health and combat) correctly?

Thanks!

Link to comment
Share on other sites

2 hours ago, SenL said:

Kzisor, what console command (while playing) to set level or give exp?

Edit:

AllPlayers[x].components.leveler:SetLevel() seems to work.

That or ThePlayer.components.leveler:SetLevel( LEVEL_SYSTEM, LEVEL ) if you're the host.

Link to comment
Share on other sites

I found a bug on mine (not yours) because each kill is not set to x (static number). Each kill is variable from victim health * victim damage divided by some arbitrary number. Because of this when I kill a treeguard for example I will get 940% exp.

I was hoping it would auto-level until it's < 100%. Now how do I do that...

Link to comment
Share on other sites

Hi Kzisor,

I'm trying to give exp when chopping but I can't seem to figure it out. It crashes.

At bottom of modmain.lua after AddReplicableComponent("leveler")

function MyCharActions(act,inst)
    local old_Chop = GLOBAL.ACTIONS.CHOP.fn
    GLOBAL.ACTIONS.CHOP.fn = function(act)
        if act.doer.prefab == "mychar" then
            if act.doer.transformed == true then
                return
            else
                if act.doer.components.leveler ~= nil then
                    act.doer.components.leveler:DoDelta("mycharlevelbadge", 0.1) --crashes here
                end
            end
        end
        
        return old_Chop(act)
    end
end

AddSimPostInit(MyCharActions)

The AddLevelSystem() is in mychar.lua file, so I'm guessing it's not yet set up at this point.

How would I do it then?

Thanks.

Link to comment
Share on other sites

15 hours ago, SenL said:

I found a bug on mine (not yours) because each kill is not set to x (static number). Each kill is variable from victim health * victim damage divided by some arbitrary number. Because of this when I kill a treeguard for example I will get 940% exp.

I was hoping it would auto-level until it's < 100%. Now how do I do that...

That isn't something that most level systems need to worry about because you don't see a level 1 killing a level 10 monster which would cause exponential experience growth. In order to combat this effect you would simply need to modify the onlevelup function to the following.

local function onlevelup( inst, data )

	if data.system == LEVEL_SYSTEM then

		if inst.components.leveler:GetLevel( LEVEL_SYSTEM ) < MAX_LEVEL then
        
			inst.components.leveler:SetLevel( LEVEL_SYSTEM, ( inst.components.leveler:GetLevel( LEVEL_SYSTEM ) + 1 ) )
			local max_exp = ( ( inst.components.leveler:GetLevel( LEVEL_SYSTEM ) * START_EXPERIENCE ) * EXPERIENCE_MULT_MAX )
			inst.components.leveler:SetMaxExperience( LEVEL_SYSTEM, max_exp ) -- Sets the max experience needed for the next level.
            
			if (data.experience >= max_exp) then -- This should be inclusive >=
				inst.components.leveler:DoDelta( LEVEL_SYSTEM, data.experience ) -- Will continue level growth.
			else
				inst.components.leveler:SetExperience( LEVEL_SYSTEM, data.experience ) -- Needs to be adjusted based on current experience over current level.
			
                                                                       
				determinestats( inst ) -- Will determine the stat increases based on leveling.
                                                                   
				-- Sets the new max values here, allows you to use determine stats in other places without setting max health every time.
				inst.components.health:DoDelta( inst.components.health.maxhealth )
		    	inst.components.hunger:DoDelta( inst.components.hunger.max )
		    	inst.components.sanity:DoDelta( inst.components.sanity.max )
			end
		end

	end

end

 

2 hours ago, SenL said:

Hi Kzisor,

I'm trying to give exp when chopping but I can't seem to figure it out. It crashes.

At bottom of modmain.lua after AddReplicableComponent("leveler")

The AddLevelSystem() is in mychar.lua file, so I'm guessing it's not yet set up at this point.

How would I do it then?

Thanks.

Actions take place on the Client, components are not loaded on the client; this is why it's crashing. You need to set up a ModRPC function and inside the action function, you would SendModRPCToServer with the ModRPC which you created.

local function ExpOnChop(inst)
	
	if inst.components.leveler then
		inst.components.leveler:DoDelta( LEVEL_SYSTEM, CHOP_EXPERIENCE )
	end

end

AddModRPCHandler(modname, "ChopExp", ExpOnChop)

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

local _Chop = GLOBAL.ACTIONS.CHOP.fn
GLOBAL.ACTIONS.CHOP.fn = function(act)
	if act.doer:HasTag("[INSERT TAG HERE]") then
		if not act.doer.transformed then
			SendModRPCToServer( GetModRPC( modname, "ChopExp" ) )
		end
	end

	return _Chop( act )
end

You should not be using AddSimPostInit in Don't Starve Together; it's been depreciated in this version of the game.

Edited by Kzisor
Fixed code error.
Link to comment
Share on other sites

I'm doing something wrong. Crashes on SendModRPCToServer()

modutil.lua 442: attempt to index local 'id_table' (a nil value)

Code:

local function ExpOnChopFn(inst)
    if inst.components.leveler ~= nil then
        inst.components.leveler:DoDelta("mycharlevelbadge", config_chopexp)
    end
end

AddModRPCHandler("gggroot", "ChopExp", ExpOnChop)

local old_Chop = GLOBAL.ACTIONS.CHOP.fn
GLOBAL.ACTIONS.CHOP.fn = function(act)
    if act.doer.prefab == "mychar" then
        if act.doer.transformed == true then
            return
        else
            SendModRPCToServer(GLOBAL.GetModRPCHandler("mychar", "ChopExp"))
        end
    end    
    return old_Chop(act)
end

 

Notes/questions:

1) I had to add "GLOBAL." before GetModRPCHandler() otherwise it crashes there. Am I missing something?

2) How come the AddModRPCHandler uses ExpOnChop where the function name is ExpOnChopFn?

(I tried both but still same crash)

 

Thanks for your continuous help!

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