New Addon in Construction: Avatar of Horror

A place to post your add ons and ideas for them

Moderator: Moderator

Post Reply
Message
Author
Lilith Dragmire
Wayist
Posts: 24
Joined: Mon Mar 20, 2017 12:03 am

New Addon in Construction: Avatar of Horror

#1 Post by Lilith Dragmire »

Hiya, I'm making a new addon for a Demented class I want to add, but I was thinking of adding some new talents and can't find a good tutorial for doing so. I did use grayswandir's tutorial on making a class, but there's no tutorial for talent making on there. If someone could link a nice easy-to-understand tutorial for that purpose, I'd appreciate a link.

EDIT: changed title of thread to add my new class name and some clarifications to first post.
EDIT 2: My class that I'm working on is Avatar of Horror, a demented sub-class. Focuses on manipulating and using horrors, including actually turning into a horror, as well as manipulating sanity. Higher level talents include possession.
Last edited by Lilith Dragmire on Wed Apr 10, 2019 6:04 pm, edited 1 time in total.

HousePet
Perspiring Physicist
Posts: 6215
Joined: Sun Sep 09, 2012 7:43 am

Re: New Addon in Construction

#2 Post by HousePet »

I usually just recommend deconstructing one of my addons to see how to do the general stuff.
Witherer is almost purely a new class mod, so you don't have to wok out which bit are for adding a class, and what is for something else.
My feedback meter decays into coding. Give me feedback and I make mods.

Lilith Dragmire
Wayist
Posts: 24
Joined: Mon Mar 20, 2017 12:03 am

Re: New Addon in Construction

#3 Post by Lilith Dragmire »

Well, I was trying to make a 'transformation' talent tree for my addon, and looked at the Ashes DLC to try and find some similar talent to modify, and decided to use the Corruption/Wrath tree talent 'Destroyer' but all the different values there just confused me. Which is why I made this thread in the first place.

HousePet
Perspiring Physicist
Posts: 6215
Joined: Sun Sep 09, 2012 7:43 am

Re: New Addon in Construction: Avatar of Horror

#4 Post by HousePet »

Feel free to ask specific questions here. You can also try the IRC channel, but that will depend on what time you are there.
My feedback meter decays into coding. Give me feedback and I make mods.

Doctornull
Sher'Tul Godslayer
Posts: 2402
Joined: Tue Jun 18, 2013 10:46 pm
Location: Ambush!

Re: New Addon in Construction: Avatar of Horror

#5 Post by Doctornull »

There's some avatar replacement code in the New Gems addon: https://te4.org/games/addons/tome/nullnewgems

It's in the Crystal racial talents, and it changes the icon to a different color -- plus one which applies a shader if you're installing a multi-hued gemstone in yourself.

I think that's what you mean by "transformation", is that right?
Check out my addons: Nullpack (classes), Null Tweaks (items & talents), and New Gems fork.

Lilith Dragmire
Wayist
Posts: 24
Joined: Mon Mar 20, 2017 12:03 am

Re: New Addon in Construction: Avatar of Horror

#6 Post by Lilith Dragmire »

Actually, no. I meant more like transformation like... like the Destroyer status buff you can get from demon statues or the Doombringer class in the Corruption/Wrath tree. You know, the one that turns you into a demon and affects your player sprite? That's the closest I can actually get to my idea in game mechanics terms.

EDIT: This, specifically: https://te4.org/wiki/Destroyer_(talent)

HousePet
Perspiring Physicist
Posts: 6215
Joined: Sun Sep 09, 2012 7:43 am

Re: New Addon in Construction: Avatar of Horror

#7 Post by HousePet »

What Doctornull is suggesting probably does what you are after, but in a different way. You could use that to compare with the Destroyer talent code to nail down exactly what does what.

I'm a bit puzzled that you were confused by all the number though. Numbers are obviously not what you want if you are changing the sprite, so just ignore them and look at what is left...

In activate:

Code: Select all

self.replace_display = mod.class.Actor.new{
	image="invis.png", add_mos = {{image = "npc/demon_major_champion_of_urh_rok.png", display_y = -1, display_h = 2}},
}
self:removeAllMOs()
game.level.map:updateMap(self.x, self.y)
And in deactivate:

Code: Select all

self.replace_display = nil
self:removeAllMOs()
game.level.map:updateMap(self.x, self.
That code is all that is needed in a timed effect to change the sprite.
My feedback meter decays into coding. Give me feedback and I make mods.

Lilith Dragmire
Wayist
Posts: 24
Joined: Mon Mar 20, 2017 12:03 am

Re: New Addon in Construction: Avatar of Horror

#8 Post by Lilith Dragmire »

Well, keep in mind, I'm relatively new to coding in Lua, so I'm mostly cannibalizing stuff from other addons.

Doctornull
Sher'Tul Godslayer
Posts: 2402
Joined: Tue Jun 18, 2013 10:46 pm
Location: Ambush!

Re: New Addon in Construction: Avatar of Horror

#9 Post by Doctornull »

Lilith Dragmire wrote:You know, the one that turns you into a demon and affects your player sprite? That's the closest I can actually get to my idea in game mechanics terms.
Ah, I didn't explain sufficiently.

The Crystal talents change your player sprite in three ways:

- When you socket a gem, your sprite changes to one of the pre-rendered per-color sprites.

- Dynamically, when you sustain Crystal Crawl, you change icon to the corresponding "crawling" icon (which is also pre-rendered, one set per color).

- Multi-Hued gems also apply a shader, which works on both of the underlying sprites: the upright and crawling ones.
Check out my addons: Nullpack (classes), Null Tweaks (items & talents), and New Gems fork.

nsrr
Sher'Tul
Posts: 1126
Joined: Mon Sep 21, 2015 8:45 pm
Location: Middle of Nowhere

Re: New Addon in Construction: Avatar of Horror

#10 Post by nsrr »

I'm still not totally sure if the part you were stuck on was the image replacement, or if all of the values in the description for the talent are causing some confusing, or what exactly. I'll help if I can and if you have specific questions about specific values, feel free to reference the code and ask. I'll attempt to give a bit of a rundown of everything that is happening with Destroyer Form.

You mentioned you were confused by some values in Destroyer. Most of it is actually handled by an effect, which I'll go over as well, and the other talents themselves. Here's the talent with some notes I've added for each field. You probably know what most these are already, but there's a lot going on in this talent due to its interaction with quite a few other talents.

Code: Select all

newTalent{
	name = "Destroyer", --Simply the display name of the talent. As this talent has no short_name field defined, it will automatically generate a short name based on this field, in this case T_DESTROYER. the short effectively acts as the id for the talent.
	type = {"corruption/wrath", 4}, -- the talent tree this talent is part of, and it's position in the tree
	require = str_corrs_req_high4, -- the requirements to learn the talent. this references a table defined in corruptions.lua, but essentially passes a table with a level and a stat requirement
	points = 5, -- the maximum number you points you may invest in the talent
	range=10, -- the range to which the target may talent. this is a buff you cast on yourself, so I have no idea why it is 10 in this case. peculiar.
	cooldown = 30, -- the number of turns that must pass between uses of the talent
	vim = 38, -- the vim cost of the talent
	--no_npc_use = true, -- when set to true, NPCs will not use this talent. this line is commented out, so NPCs may use the talent.
	no_energy=true, -- when set to true, the talent will use no turn energy, i.e. it is instant to use
	tactical = { BUFF = 3 }, -- tells the AI this talent is a buff with a priority of 3. I'm really not clear on how these are actually used, but I've never really dug into it too far
	getDuration = function(self, t) return  math.floor(self:combatTalentScale(t, 4, 7)) end, -- this is a function defined specifically for this talent that will be called by the talent action to determine the duration of the buff. at effective talent level 1 the duration will be 4, at effective talent level 5 it will be 7
	getDestroy = function(self, t) return  util.bound(self:getTalentLevelRaw(t), 1, 5) end, -- another function defined for this talent, it will return the raw talent level limited between 1 and 5. this number is used by the buff set in the action.
	getPower = function(self, t) return 5 + math.ceil(self:combatTalentSpellDamage(t, 6, 30)) end, -- same as the above, except this returns a value which scales with talent level and spellpower. at effective talent level 1 and 10 spellpower this will be 6, at effective talent level 5 and 100 spellpower it will be 30
	action = function(self, t) -- defines what will happen when the talent is used
		self:setEffect(self.EFF_DESTROYER_FORM, t.getDuration(self, t), {power=t.getPower(self, t), destroyer_level = t.getDestroy(self, t)}) -- sets the destroyer form effect on the payer, passing the duration from getDuration function above, and passes the parameters 'power' and 'destroyer_level' from the respective functions above
		game:playSoundNear(self, "talents/fire") -- plays a sound clip
		return true -- active talents have to return true in order for the talent cooldown to start, resources consumed, etc
	end,
	info = function(self, t) -- defines the description for the talent. all of the %d and %0.1f are variables provided via the format at the end of the description
		local dest = t.getDestroy(self, t) -- just creates a variable called 'dest' that takes the value returned by getDestroy, which will be the raw talent level (limited from 1 - 5)
		return ([[Your body overflows with the power of the Fearscape, turning you into a powerful demon for %d turns. This increases your stamina regen and physical power by %d, and your disarm and stun immunity by %d%%.
		The physical power, stamina regen, and status resistances increase with your spellpower.
		Your other talents also gain a variety of bonuses:
		-Draining Assault: Reduces cooldown by %d.
		-Reckless Strike: Gain %d%% resistance penetration for all elements for %d turns.
		-Obliterating Smash: Increases range by %d.
		-Abduction: If it hits, get an additional %d attacks at 35%% weapon damage.
		-Incinerating Blows: Increases chance of bonus damage to %d%%.
		-Fearfeast: Gain %0.1f vim per stack.
		-Maw of Urh'rok: Increases cone width by %d degrees.]]):
		format( -- this tells the description what numbers to plug into the variables in the text of the description
			t.getDuration(self, t), -- the duration of the destroyer form effect. the value from getDuration.
			t.getPower(self, t), -- the physical power and stamina regen bonus. the value from getPower.
			math.min(math.ceil(t.getPower(self, t)/40 *100),100), -- the status immunity bonus -- the lower of 100 and getPower divided by 40 times 100
			math.ceil(dest/3), -- the cooldown reduction for Draining Assault. raw talent level (limited from 1-5) divided 3 and 'rounded' up
			10 * dest, 3 + math.ceil(dest/2), -- the power of the resistance penetration bonus from reckless strike, and the duration. raw talent level (limited from 1-5) times 10, 3 + raw talent level (limited from 1-5) divided by 3 and 'rounded' up
			math.ceil(dest/4), -- increased range for Obliteration Smash raw talent level (limited from 1-5) divided  by 4 and 'rounded' up
			math.ceil(dest/2), -- number of extra strikes from Abduction. raw talent level (limited from 1-5) divided by 2 and 'rounded' up
			25 + 10 * dest, -- increased chance of bonus damage for Incinerating Blows. 25 plus 10 times raw talent level (limited from 1-5)
			dest * 0.4, -- extra vim gained per stack for Fearfeast. raw talent level (limited from 1-5) times 0.4
			dest * 10 -- increased cone width in degrees for Maw of Urh'rok. raw talent level (limited from 1-5) times 10
		)
	end,
}
This is the code for the effect, with comments added.

Code: Select all

newEffect{
	name = "DESTROYER_FORM", image = "talents/destroyer.png", -- the id for the effect, and the image to use for the effect
	desc = "Destroyer", -- the displayed name of the effect
	long_desc = function(self, eff) return ("The target assumes the form of a powerful demon."):format() end, -- the tooltip displayed when you mouse over the effect icon
	type = "magical", -- the type of effect (e.g. physical, mental, magical, other)
	subtype = { fire=true }, -- a field which contains the subtype(s) for the effect. most effects have one or two at most, these are mostly used with cleanse that target a specific subtype of effect, like wound or disease. probably is not actually used at all in this case.
	status = "beneficial", -- the nature of the status, e.g. a 'beneficial' buff or a 'detrimental' debuff
	parameters = {power = 5, destroyer_level = 1}, -- parameters the talent will use if it is not passed other parameters when set. essentially a failsafe
	on_gain = function(self, err) return "#Target# turns into a demon!", "+Destroyer" end, -- the text that will display when an actor gains the effect
	on_lose = function(self, err) return "#Target# is no longer transformed.", "-Destroyer" end, -- the text that will display when an actor loses the effect
	activate = function(self, eff) -- this defines what the effect will actually do when set. in this case, grant some temporary values and change the player sprite
		self:effectTemporaryValue(eff, "stamina_regen", eff.power) -- increases stamina regen by the 'power' parameter of the effect, which was called from getPower in the Destoyer talent
		self:effectTemporaryValue(eff, "combat_dam", eff.power) -- combat_dam is Physical Power, which is a little confusing. increases your Physical Power by the power parameter
		self:effectTemporaryValue(eff, "stun_immune", eff.power/40) -- increases stun resistance by power divided by 40
		self:effectTemporaryValue(eff, "disarm_immune", eff.power/40) -- increases disarm resistance by power divided by 40
		self:effectTemporaryValue(eff, "is_destroyer", eff.destroyer_level) -- sets a value called 'is_destroyer' on the actor equal to the destroyer_level parameter, which will be equal to the raw talent level of Destroyer Form (limited from 1-5)
		self:effectTemporaryValue(eff, "demon", 1) -- sets  the 'demon' value on the actor, indicating it should be treated as a demon (important for demonfire damage and fearscape in particular)
		self:effectTemporaryValue(eff, "size_category", 2) -- increases size category by 2

		self.replace_display = mod.class.Actor.new{
			image="invis.png", add_mos = {{image = "npc/demon_major_champion_of_urh_rok.png", display_y = -1, display_h = 2}}, -- this makes the actor sprite invisible and sets an image file to be displayed on top of the actor, with adjusted x and y value. i believe this is where the image will center relative to the center of the tile
		}
		self:removeAllMOs() -- removes shaders? honestly, not totally sure, but it has do with making the game actually update the displayed image
		game.level.map:updateMap(self.x, self.y) -- again, updates the display of the image and shaders
	end,
	deactivate = function(self, eff) -- what will happen when the effect is deactivated. automatically removes values that are added with the effectTemporaryValue syntax.
		self.replace_display = nil -- sets the replace_display field to nil
		self:removeAllMOs() -- updates display
		game.level.map:updateMap(self.x, self.y) -- updates display
	end,
}
So the effect basically handles the stamina regen and physical power, as well as swapping the image and indicating you are a demon. All of the talent-specific effets are actually coded into those talents. I won't go through every one, but here's Abduction for an example. I'll just point out where it's referencing Destroyer Form, inside the action field.

Code: Select all

newTalent{
	name = "Abduction",
	type = {"corruption/torture", 2},
	require = str_corrs_req2,
	points = 5,
	cooldown = 10,
	random_ego = "attack",
	stamina = 25,
	vim = 14,
	range = function(self, t) return  math.ceil(self:combatTalentScale(t, 3.75, 8)) end,
	tactical = { CLOSEIN = 3, DISABLE = 1, ATTACK = {weapon = 1}, },
	requires_target = true,
	getSmallhit = function (self, t) return self:combatTalentWeaponDamage(t, 0.7, 1.05) end,
	getBighit = function (self, t) return self:combatTalentWeaponDamage(t, 1, 1.35) end,
	on_pre_use = function(self, t, silent) if not self:hasTwoHandedWeapon() then if not silent then game.logPlayer(self, "You require a two handed weapon to use this talent.") end return false end return true end,
	action = function(self, t)
		local tg = {type="bolt", range=self:getTalentRange(t)}
		local x, y = self:getTarget(tg)
		if not x or not y then return nil end
		if core.fov.distance(self.x, self.y, x, y) > self:getTalentRange(t) then return nil end
		if not game.level.map.seens(x, y) or not self:hasLOS(x, y) then return nil end
		local had_effect = false
		self:project(tg, x, y, function(px, py)
			local target = game.level.map(px, py, engine.Map.ACTOR)
			if not target then return end
			had_effect = true
			if self:attackTarget(target, nil, t.getSmallhit(self,t), true) and not target.dead then
				target:pull(self.x, self.y, tg.range)
				self:attackTarget(target, nil, t.getBighit(self,t), true)
				if self:attr("is_destroyer") then -- this checks to see if the actor has the 'is_destroyer' value, which is set by the Destroyer Form effect
					for i = 1, math.ceil(self.is_destroyer/2) do -- this will repeat the loop from i = 1 to i = 'is_destroyer' divided by 2 and 'rounded' up. tracing back, this value is originally called by getDestroy in the Destroyer talent and passed to the Destroyer Form effect and will be equal to the raw talent level, limited from 1  to 5.
						if not target.dead then self:attackTarget(target, nil, 0.35, true) end -- attacks the target with a 35% power weapon attack, if it is not dead
					end
				end
			end
		end)
		return had_effect
	end,
	info = function(self, t)
		return ([[Hits the target doing %d%% weapon damage. If the attack hits, you pull the target in and strike them again, dealing another %d%% weapon damage.]]):format(100 * t.getSmallhit(self, t), 100 * t.getBighit(self, t))
	end,
}
To create a similar talent, you would likely want to set it up in similar manner, with the talent calculating the values and passing them to an effect you will set on the player to handle the sprite replacement and any temporary values you also want to impart.

Hopefully this clears some things up, or at least gives you a jumping off point for more questions. Feel free to ask.

Another example from vanilla ToME would be Shivgoroth Form in the Spells/Ice tree, which functions in a very similar manner.

Post Reply