ToME: the Tales of Maj'Eyal

Everything about ToME
It is currently Mon Feb 24, 2020 8:26 am

All times are UTC




Post new topic Reply to topic  [ 10 posts ] 
Author Message
PostPosted: Tue Apr 09, 2019 10:03 pm 
Offline
Wayist

Joined: Mon Mar 20, 2017 12:03 am
Posts: 23
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.

Top
 Profile  
 
PostPosted: Tue Apr 09, 2019 11:17 pm 
Offline
Perspiring Physicist

Joined: Sun Sep 09, 2012 7:43 am
Posts: 6032
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.


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 6:00 pm 
Offline
Wayist

Joined: Mon Mar 20, 2017 12:03 am
Posts: 23
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.


Top
 Profile  
 
PostPosted: Wed Apr 10, 2019 10:32 pm 
Offline
Perspiring Physicist

Joined: Sun Sep 09, 2012 7:43 am
Posts: 6032
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.


Top
 Profile  
 
PostPosted: Fri Apr 12, 2019 12:09 am 
Offline
Sher'Tul Godslayer

Joined: Tue Jun 18, 2013 10:46 pm
Posts: 2402
Location: Ambush!
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.


Top
 Profile  
 
PostPosted: Mon Apr 15, 2019 10:39 pm 
Offline
Wayist

Joined: Mon Mar 20, 2017 12:03 am
Posts: 23
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)


Top
 Profile  
 
PostPosted: Tue Apr 16, 2019 1:10 am 
Offline
Perspiring Physicist

Joined: Sun Sep 09, 2012 7:43 am
Posts: 6032
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:
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:
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.


Top
 Profile  
 
PostPosted: Tue Apr 16, 2019 1:40 am 
Offline
Wayist

Joined: Mon Mar 20, 2017 12:03 am
Posts: 23
Well, keep in mind, I'm relatively new to coding in Lua, so I'm mostly cannibalizing stuff from other addons.


Top
 Profile  
 
PostPosted: Wed Apr 17, 2019 8:16 pm 
Offline
Sher'Tul Godslayer

Joined: Tue Jun 18, 2013 10:46 pm
Posts: 2402
Location: Ambush!
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.


Top
 Profile  
 
PostPosted: Thu Apr 18, 2019 12:32 am 
Offline
Uruivellas

Joined: Mon Sep 21, 2015 8:45 pm
Posts: 838
Location: Middle of Nowhere
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:
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:
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:
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 10 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group