Sign in to follow this  
SenL

Example/template of character mod with level system?

Recommended Posts

SenL    29

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

Share this post


Link to post
Share on other sites
Kzisor    1058

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

Share this post


Link to post
Share on other sites
SenL    29

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

Share this post


Link to post
Share on other sites
Kzisor    1058
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.

 

Share this post


Link to post
Share on other sites
SenL    29

Got it.

So what's the difference between onload and onpreload?

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

Share this post


Link to post
Share on other sites
Kzisor    1058
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.

Share this post


Link to post
Share on other sites
SenL    29

Ok. Thanks.

Another question.

How come there is no onsave?

Edit:

Nevermind, it's handled in the component (leveler) self:OnSave()

Edited by SenL

Share this post


Link to post
Share on other sites
Kzisor    1058
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.

Share this post


Link to post
Share on other sites
SenL    29

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)

Share this post


Link to post
Share on other sites
Kzisor    1058
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

Share this post


Link to post
Share on other sites
SenL    29

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

Share this post


Link to post
Share on other sites
Kzisor    1058
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.

Share this post


Link to post
Share on other sites
SenL    29

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

Share this post


Link to post
Share on other sites
Kzisor    1058
4 minutes ago, SenL said:

No more crash.

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

I agree that debug printouts are always important when you're not seeing results you might want to see.

Share this post


Link to post
Share on other sites
SenL    29

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!

Share this post


Link to post
Share on other sites
Kzisor    1058
4 minutes ago, SenL said:

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

Should be data.victim.components.health; data.inst is a nil value.

Share this post


Link to post
Share on other sites
SenL    29

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

Edit:

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

Edited by SenL

Share this post


Link to post
Share on other sites
Kzisor    1058
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.

Share this post


Link to post
Share on other sites
SenL    29

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

Share this post


Link to post
Share on other sites
SenL    29

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.

Share this post


Link to post
Share on other sites
Kzisor    1058
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.

Share this post


Link to post
Share on other sites
SenL    29

Oh man, I would have never figured out how. Lol.

I copied from "Starve Wars" btw, but they have TheForce component.

Ok let me try those... 

Share this post


Link to post
Share on other sites
Kzisor    1058
48 minutes ago, SenL said:

Oh man, I would have never figured out how. Lol.

I copied from "Starve Wars" btw, but they have TheForce component.

Ok let me try those... 

Please let me know if you have anymore questions.

Share this post


Link to post
Share on other sites
SenL    29

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!

Share this post


Link to post
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
Sign in to follow this