Jump to content

Recommended Posts

Hello,

I have been trying to implement a custom badge for my character.
It is essentially a meter to keep track of his "barrier", but before I can try to implement this I would like to make a badge for it.
I looked through other modded characters and forum posts and ended up using most of my code from "ink sans".
However, when in game the badge doesn't show.
I can't tell if the issue is based from my 'anim' file, or something in the code.
I looked through each and every function in "ink sans" emotion badge code, through a component, a badge widget, and a "display" widget that seems unorthodox.
I checked through my character prefab, gave the correct components, added the functions that were in "ink sans" character prefab, renamed all of the variables accordingly.
I also added in the emotions.zip anim file from "ink sans" and renamed it to my corresponding component.
I can't tell what specifically goes wrong, and why the badge won't show, if it is an anim zip file issue I do not know where to start to make a custom badge's anim file, as I can't find any proper guides or forum posts on the topic.

There is also a second issue as well, in which barrierstrong.lua (my component file) loads data incorrectly.
I don't know why this is happening, I don't fully understand the component's code and I don't see why the data messes up when I look at it.
This is the error: [string "../mods/Ward Rowan/scripts/components/barri..."]:99: attempt to index local 'data' (a number value)

If anyone here can figure out the problem I would appreciate it very much.
I have been stuck with these two issues for days and cannot figure it out.

Below are the 4 lua files I believe to be relevant to the issue, and if there isn't anything wrong then the entire character is attached as a zip file.
Apologies for leftover code staying commented out at certain places, and for the mess.

barrierbadge.lua (widget)

Spoiler
local Badge = require "widgets/badge"
local UIAnim = require "widgets/uianim"

local BarrierBadge = Class(Badge, function(self, owner)
	Badge._ctor(self, "barrierstrong", owner)
	
	self.barriersarrow = self.underNumber:AddChild(UIAnim())
	self.barriersarrow:GetAnimState():SetBank("sanity_arrow")
	self.barriersarrow:GetAnimState():SetBuild("sanity_arrow")
	self.barriersarrow:GetAnimState():PlayAnimation("neutral")
	self.barriersarrow:SetClickable(false)

	self.owner = owner
	owner.barriersarrow = self.barriersarrow

	self.val = 0
	self.max = 100
	self.arrowdir = nil

	self:StartUpdating()
end)

function BarrierBadge:SetPercent(val, max, penaltypercent)
	self.val = val
	self.max = max
	Badge.SetPercent(self, self.val, self.max)
end

function BarrierBadge:OnUpdate(dt)
	local percent = self.owner.currentbarriers:value() / self.owner.maxbarriers:value()
	local GetPercent = TheWorld.ismastersim and self.owner.components.barrierstrong ~= nil and 
		self.owner.components.barrierstrong:GetPercent() or percent
	local anim = nil
	anim = "neutral"

	if anim and self.arrowdir ~= anim then
		self.arrowdir = anim
		self.barriersarrow:GetAnimState():PlayAnimation(anim, true)
	end
end

return BarrierBadge

barrierdisplay.lua (widget)

Spoiler
local function BarriersStatusDisplays(self)
	if self.owner.prefab == "wardrowan" then
		local BarrierBadge = GLOBAL.require "widgets/barrierbadge"
		self.barrierstrong = self:AddChild(BarrierBadge(self.owner))
		self.owner.barrierbadge = self.barrierstrong
		self._custombadge = self.barrierstrong --For the Status Announcements mod
		
		local BP = self.brain:GetPosition()
		local AOS = false
		for k,v in ipairs(GLOBAL.KnownModIndex:GetModsToLoad()) do 
			local Mod = GLOBAL.KnownModIndex:GetModInfo(v).name
			if Mod == "Combined Status" then 
				AOS = true
			end
		end
		if AOS then
			self.barrierstrong:SetPosition(-62, -52, 0)
		else
	    	self.barrierstrong:SetPosition(-40,-50,0)
		    self.brain:SetPosition(BP.x + 40, BP.y - 10, 0)
		end

		local function OnSetPlayerMode(self)
			if self.onbarriersdelta == nil then
				self.onbarriersdelta = function(owner, data) self:BarriersDelta(data) end
				
				self.inst:ListenForEvent("barrierstrongdelta", self.onbarriersdelta, self.owner)
				if self.owner.components.barrierstrong ~= nil then
					self:SetBarriersPercent(self.owner.components.barrierstrong:GetPercent())
				end
			end
		end

		local function OnSetGhostMode(self)
			if self.onbarriersdelta ~= nil then
				self.inst:RemoveEventCallback("barriersdelta", self.onbarriersdelta, self.owner)
				self.onbarriersdelta = nil
			end
		end

		self._SetGhostMode = self.SetGhostMode
		function self:SetGhostMode( ghostmode )
			self._SetGhostMode( self, ghostmode )
			if ghostmode then
				self.barrierstrong:Hide()
				OnSetGhostMode(self)
			else
				self.barrierstrong:Show()
				OnSetPlayerMode(self)
			end
		end
		
		function self:SetBarriersPercent(pct)
			if self.owner.components.barrierstrong ~= nil then
				self.barrierstrong:SetPercent(pct, self.owner.components.barrierstrong.max)
			end

			--[[
			if pct <= 0.2 then
				self.emotions:StartWarning()
			else
				self.emotions:StopWarning()
			end
			]]-- 
		end

		function self:BarriersDelta(data)
			self:SetBarriersPercent(data.newpercent)

			if not data.overtime then
				if data.newpercent > data.oldpercent then
					self.barrierstrong:PulseGreen()
					GLOBAL.TheFrontEnd:GetSound():PlaySound("dontstarve/HUD/health_up")
				elseif data.newpercent < data.oldpercent then
					GLOBAL.TheFrontEnd:GetSound():PlaySound("dontstarve/HUD/health_down")
					self.barrierstrong:PulseRed()
				end
			end
		end

		OnSetPlayerMode(self)
	end
end
AddClassPostConstruct("widgets/statusdisplays", BarriersStatusDisplays)

barrierstrong.lua (component file)

Spoiler
local function onmax(self, max)
    if self.inst ~= nil then
        assert(max >= 0 and max <= 65535, "Player maxbarriers out of range: "..tostring(max))
        self.inst.maxbarriers:set(math.ceil(max))
    end
end

local function oncurrent(self, current)
    if self.inst ~= nil then
        assert(current >= 0 and current <= 65535, "Player currentbarriers out of range: "..tostring(current))
        self.inst.currentbarriers:set(math.ceil(current))
    end
end

local BarrierStrong = Class(function(self, inst)
	self.inst = inst

	inst:AddTag("barrierstrong")

	self.max = 100
	self.current = self.max
	self._old = self.current
	self.rate = 0
	self.inst:StartUpdatingComponent(self)
end,
nil,
{
	max = onmax,
	current = oncurrent,
})

function BarrierStrong:GetMax()
	return self.max
end

function BarrierStrong:GetDelta()
	return self.rate
end

function BarrierStrong:GetRate()
	return self.rate
end

function BarrierStrong:DoDelta(delta, overtime, ignore_invincible)

    local old = self._old
    self.current = math.clamp(self.current + delta, 0, self.max)
    self._old = self.current

    if self.current < 0 then 
        self.current = 0
    elseif self.current > self:GetMax() then
        self.current = self:GetMax()
    end

    self.inst:PushEvent("barrierstrongdelta", { oldpercent = old / self.max, newpercent = self.current / self.max, overtime = overtime })
end

function BarrierStrong:GetPercent()
    return self.current / self.max
end

function BarrierStrong:SetPercent(percent, overtime)
    self.current = self.max * percent
    self:DoDelta(0, overtime)
end

function BarrierStrong:OnUpdate(dt)
	self:Recalc(dt)
end

function BarrierStrong:Recalc(dt)

	local barrierstrong_supply = self.barrierstrong_supply or 0	
	
	local supply_delta = barrierstrong_supply*1

	self.rate = -0.1
	
	if self.custom_rate_fn then
		self.rate = self.rate + self.custom_rate_fn(self.inst)
	end
	
	self:DoDelta(self.rate*dt, true)
end

function BarrierStrong:LongUpdate(dt)
	self:OnUpdate(dt)
end

function BarrierStrong:OnSave()
	return 
	{
		current = self.current
	}
end

function BarrierStrong:OnLoad(data)
	if data ~= nil and data.current ~= nil then
		self.current = data.current
		self:DoDelta(0, true)
	end
end

function BarrierStrong:GetDebugString()
    return string.format("%2.2f / %2.2f at %2.4f", self.current, self.max, self.rate)
end

function BarrierStrong:SetMax(amount)
    self.max = amount
    self:DoDelta(0, true)
end

return BarrierStrong

wardrowan.lua (prefab file)

Spoiler
local MakePlayerCharacter = require "prefabs/player_common"

local assets = {
    Asset("SCRIPT", "scripts/prefabs/player_common.lua"),
}


-- Your character's stats
TUNING.WARDROWAN_HEALTH = 200
TUNING.WARDROWAN_HUNGER = 150
TUNING.WARDROWAN_SANITY = 150
TUNING.WARDROWAN_DAMAGE_MULT = 1.1
TUNING.WARDROWAN_DAMAGE_ABSORB = 0.1
TUNING.WARDROWAN_HUNGER_RATE = 1
TUNING.WARDROWAN_DAPPERNESS = 3.3
TUNING.WARDROWAN_SANITY_DAMAGE_RATE = 1
TUNING.WARDROWAN_SANITY_DAMAGE_OVERTIME_RATE = 0

-- Custom starting inventory
TUNING.GAMEMODE_STARTING_ITEMS.DEFAULT.WARDROWAN = {
	"flint",
	"flint",
	"twigs",
	"twigs",
}

local start_inv = {}
for k, v in pairs(TUNING.GAMEMODE_STARTING_ITEMS) do
    start_inv[string.lower(k)] = v.WARDROWAN
end
local prefabs = FlattenTree(start_inv, true)

local function onbarriersdelta(inst, data)
	if inst:HasTag("playerghost") or inst.components.health:IsDead() then
		return
	end

end

-- When the character is revived from human
local function onbecamehuman(inst)
	-- Set speed when not a ghost (optional)
	inst.components.locomotor:SetExternalSpeedMultiplier(inst, "wardrowan_speed_mod", 1)
end

local function onbecameghost(inst)
	-- Remove speed modifier when becoming a ghost
   inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "wardrowan_speed_mod")
   
   inst.components.barrierstrong:SetPercent(0.4333)
end

local function onbarriersdeltaNet(parent, data)
    if data.overtime then
    elseif data.newpercent > data.oldpercent then
        SetDirty(parent.isbarrierspulseup, true)
    elseif data.newpercent < data.oldpercent then
        SetDirty(parent.isbarrierspulsedown, true)
    end
end

local function OnBarriersDirty(inst)
    if not TheWorld.ismastersim and inst.barrierbadge ~= nil then
		local oldpercent = inst._oldbarrierspercent
		if oldpercent == nil then oldpercent = 0 end
		local percent = inst.currentbarriers:value() / inst.maxbarriers:value()
		local data =
		{
			oldpercent = oldpercent,
			newpercent = percent,
			overtime =
				not (inst.isbarrierspulseup:value() and percent > oldpercent) and
				not (inst.isbarrierspulsedown:value() and percent < oldpercent),
		}
		inst._oldbarrierspercent = percent
		inst.isbarrierspulseup:set_local(false)
		inst.isbarrierspulsedown:set_local(false)
		inst:PushEvent("barriersdelta", data)
		inst.barrierbadge:SetPercent(percent, inst.maxbarriers:value())
	end
end

-- When loading or spawning the character
local function onload(inst, data)
    inst:ListenForEvent("ms_respawnedfromghost", onbecamehuman)
    inst:ListenForEvent("ms_becameghost", onbecameghost)

    if inst:HasTag("playerghost") then
        onbecameghost(inst)
    else
        onbecamehuman(inst)
    end
end

local function OnSave(inst,data)
	data.barrierstrong = inst.barrierstrong:value()
end

local function OnBarriersDirty(inst)
    if not TheWorld.ismastersim and inst.barrierbadge ~= nil then
		local oldpercent = inst._oldbarrierspercent
		if oldpercent == nil then oldpercent = 0 end
		local percent = inst.currentbarriers:value() / inst.maxbarriers:value()
		local data =
		{
			oldpercent = oldpercent,
			newpercent = percent,
			overtime =
				not (inst.isbarrierspulseup:value() and percent > oldpercent) and
				not (inst.isbarrierspulsedown:value() and percent < oldpercent),
		}
		inst._oldbarrierspercent = percent
		inst.isbarrierspulseup:set_local(false)
		inst.isbarrierspulsedown:set_local(false)
		inst:PushEvent("barrierstrongdelta", data)
		inst.barrierbadge:SetPercent(percent, inst.maxbarriers:value())
	end
end

local function OnEquip(inst, data)
	if data ~= nil and data.item ~= nil then
		if data.item.components.armor ~= nil then
			inst.components.inventory:DropItem(data.item)
			inst.components.talker:Say(({"That will not be necessary.", "I don't have any need for armor.", "My barrier is far better."})[math.random(3)])
			-- inst.components.inventory:Unequip(data.item.components.equippable.equipslot)
		end
	end
end

local function OnHealthDelta(inst, data)
    if data.amount < 0 then
        inst.components.sanity:DoDelta(data.amount * ((data ~= nil and data.overtime) and TUNING.WARDROWAN_SANITY_DAMAGE_OVERTIME_RATE or TUNING.WARDROWAN_SANITY_DAMAGE_RATE))
		inst.components.barrierstrong:DoDelta(data.amount * ((data ~= nil and data.overtime) and TUNING.WARDROWAN_SANITY_DAMAGE_OVERTIME_RATE or TUNING.WARDROWAN_SANITY_DAMAGE_RATE))
    end
end


-- This initializes for both the server and client. Tags can be added here.
local common_postinit = function(inst) 
	-- Minimap icon
	inst.MiniMapEntity:SetIcon( "wardrowan.tex" )
	
	-- Tags
	inst:AddTag("nowormholesanityloss")
	
	inst.currentbarriers = net_ushortint(inst.GUID, "barrierstrong.current", "barrierstrongdirty")
    inst.maxbarriers = net_ushortint(inst.GUID, "barrierstrong.max", "barrierstrongdirty")
    inst.isbarrierspulseup = net_bool(inst.GUID, "barrierstrong.dodeltaovertime(up)", "barrierstrongdirty")
    inst.isbarrierspulsedown = net_bool(inst.GUID, "barrierstrong.dodeltaovertime(down)", "barrierstrongdirty")
	if not TheWorld.ismastersim then
		inst:ListenForEvent("barriersdirty", OnBarriersDirty)
	else
		inst:ListenForEvent("barriersdelta", onbarriersdeltaNet, inst.entity:GetParent())
	end
	
	
	inst.barrierstrong = net_ushortint(inst.GUID, "barrierstrong", "barrierstrongdirty" )
	 --net_ushortint is the typical use of stats

	 --"name" is the name of the netvariable
	 --"namesdirty" is an Event which is called whenever this is changed

	inst.barrierstrong:set(100)
	 -- inst.name:set(number) [same as] inst.name = number

	inst.barrierstrong:value()
	 --getting the value of the net_variable 
	
end

-- This initializes for the server only. Components are added here.
local master_postinit = function(inst)
	-- Set starting inventory
    inst.starting_inventory = start_inv[TheNet:GetServerGameMode()] or start_inv.default
	
	-- choose which sounds this character will play
	inst.soundsname = "willow"
	
	-- Uncomment if "wathgrithr"(Wigfrid) or "webber" voice is used
    --inst.talker_path_override = "dontstarve_DLC001/characters/"
	
	-- Setting Basic Stats
	inst.components.health:SetMaxHealth(TUNING.WARDROWAN_HEALTH)
	inst.components.hunger:SetMax(TUNING.WARDROWAN_HUNGER)
	inst.components.sanity:SetMax(TUNING.WARDROWAN_SANITY)
	
	inst:AddComponent("barrierstrong")
	
	-- Listen for events
	inst:ListenForEvent("healthdelta", OnHealthDelta) -- Damage on hit sanity loss
	inst:ListenForEvent("equip", OnEquip)
	
	-- Damage Dealt and Taken Modifiers
    inst.components.combat.damagemultiplier = 1 * TUNING.WARDROWAN_DAMAGE_MULT
	inst.components.health.absorb = 1 * TUNING.WARDROWAN_DAMAGE_ABSORB
	
	-- Hunger Rate and Favorite Food
	inst.components.hunger.hungerrate = TUNING.WILSON_HUNGER_RATE * TUNING.WARDROWAN_HUNGER_RATE
	inst.components.foodaffinity:AddPrefabAffinity("honeyham", TUNING.AFFINITY_15_CALORIES_HUGE)
	
	-- Sanity Regen
	inst.components.sanity.dapperness = 0.016 * TUNING.WARDROWAN_DAPPERNESS
	inst.components.sanity:SetNegativeAuraImmunity(true)
	
	inst.OnLoad = onload
	inst.OnSave = OnSave
    inst.OnNewSpawn = onload
	
end

return MakePlayerCharacter("wardrowan", prefabs, assets, common_postinit, master_postinit, prefabs)

Also, in modmain.lua I have this snippet of code at the bottom of the file, as the other mod had it.

Spoiler
modimport("scripts/widgets/barrierdisplay.lua")

Again, sorry for the messy bloated code, and I would be grateful for help.

Thank you.

Ward Rowan.zip

Edited by Locke_C

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
×
  • Create New...