How to add talent sources to the Talent Point Planner dialog
============================================================

So, your addon provides a way of gaining talent points or talents or talent
categories, and you'd like those sources to be available in the talent
point planner so that the player can plan their build around gaining those
bonuses.  Well, first off, thanks for helping to make the Talent Point
Planner addon more useful. ;)  This documentation should include all (or
hopefully at least most of) the information you'll need; if you have more
questions, the forums.te4.org thread for the Talent Point Planner addon
(<https://forums.te4.org/viewtopic.php?f=50&t=34113>) is a good place to
ask them.

Main entry hooks
----------------

There are three hooks invoked by the planner dialog.  For all of them, the
'self' argument is the planner dialog; their data{} fields are described
below.

  TalentPointPlanner:flags
	Called early in PointPlanDialog:init() to populate the dialog's
	flags{} table, described in the "The planner dialog and why you
	care about it" section below.  You can add flags here to provide
	useful information for your talent source's methods.
	data{} fields:

	  dialog	The planner dialog.

	  actor		The actor being planned for.

	  ref_actor	The player, which may be different from 'actor'.

	  flags		The table of flags being built.  Your hook can add
	  		values here.

  TalentPointPlanner:talentSource
	Called from PointPlanDialog:generateSourcesList() when building or
	rebuilding the list of talent sources to display.  This is where
	you can add your talent sources.
	data{} fields:

	  dialog	The planner dialog.

	  actor		The actor being planned for.

	  ref_actor	The player, which may be different from 'actor'.

	  flags		The planner dialog's flags{} table.  This hook can
			be called multiple times, so you probabaly don't
			want to add flag values here.

	  sources	The list of talent sources being built.  Your hook
	  		can add your talent sources to this list.

	Each talent source you add will be a table with various fields and
	methods, as described in detail below.

  TalentPointPlanner:postProcess
	Called from PointPlanDialog:startPlanDialog() just before the
	planner dialog opens a level-up dialog on its player clone, after
	all talent sources have been applied.  If your talent sources
	require any unusual ad hoc modifications to the player clone, this
	may be a suitable place to apply them.
	data{} fields:

	  dialog	The planner dialog.

	  actor		The actor being planned for, unmodified.  Your hook
			should not alter this.

	  actor_dup	The clone of the actor being planned for, with all
			talent sources applied and talent points spent to
			match the point plan.  Your hook may alter this
			copy as needed.

We also provide two more hooks which some implementers will find useful:

  TalentPointPlanner:chatAnswer
	Called from engine.dialogs.Chat:use() when the player selects an
	answer in a chat dialog, just before the answer's .action()
	callback (if any) is called.

	self is: The engine.dialogs.Chat chat dialog
	data{} fields:

	  dialog	The chat dialog.
	  item		The list entry in the chat dialog corresponding to
			the answer the user selected.  Will be nil for
			"auto-activating" chat stages.
	  chat		The engine.Chat chat definition object.
	  id		The ID of the currently active chat stage.
	  answer	The answers{} table entry for the answer the player
			selected.  As per standard engine.Chat structure,
			answer[1] will be the text of the selected answer,
			and the answer's .jump and .action() fields, if
			any, will also be present.

  TalentPointPlanner:playerUseObject
	Called from mod.class.Player:playerUseObject() after the player
	uses an item.
	[Disclaimer:  This hook is only known to work with objects that are
	 consumed on use by way of returning {destroy=true} from their
	 .use() method.  It is anticipated that most items of interest will
	 be single-use anway, so we hope it will be sufficient.]

	self is: The actor using the object.
	data{} fields:

	  actor		The actor using the object
	  object	The object that was just used
	  item		The 'item' argument passed to :playerUseObject()
	  inven		The 'inven' argument passed to :playerUseObject()

We'll discuss these hooks further in the ":hasSeen() and :hasDone()"
section below.


Required fields and methods
---------------------------

Your source definitions must contain the following fields and methods.  In
all of the following method definitions, 'd' is the planner dialog, 'def'
is the source definition table, and 'cfg' is optional configuration infor-
mation associated with the talent source (as described in the "Configuration
dialog" section below).

  tag:
	The identifying tag for this talent source, used to store it in the
	point plan.  Must be unique.

  desc or desc(d, def, cfg):
	The display name for this talent source.  This is what is shown in
	the planner dialog, prefaced by either "Include" or "You already
	have".  Can be a string or a method that returns a string.

  available(d, def):
	Returns true if the character has access to this source.  For
	instance, available() for the Cursed/Cursed Aura tree only returns
	true for Afflicted characters.

  known(d, def):
	Returns true if the player should know about this talent source.
	This is mainly to avoid spoilers, like not showing the Arena's
	generic point reward to players who haven't unlocked the Arena.
	If you don't care about that, you can have this method just return
	true.

  done(d, def):
	Returns true if the current character has received the bonuses from
	this talent source.

  use(p, def, cfg):
	Applies a simulation of this talent source's bonuses to the planner
	dialog's player clone, passed in as 'p'.


The planner dialog and why you care about it
--------------------------------------------

As you can see, most of the above methods pass in the planner dialog as an
argument.  This is because the dialog contains information that your
methods will probably need, and also defines some utility methods (listed
in "Planner dialog utility methods" below) that can help in writing your
methods.  In particular, the dialog has the following fields:

  d.actor:
	The actor being planned for.

  d.ref_actor:
	The actual player actor.  This will be different from d.actor if
	the actor being planned for is a sidekick of the player (the
	Alchemist's golem, for instance).

  d.actor_descriptors:
  	The descriptor names for the actor being planned for.  You can use
	this to check for things like race or class; for instance,
	'd.actor_descriptors.subclass == "Archmage"'.

  d.flags{}:
	Assorted useful information.  Currently supported flags:

	  campaign:
		The campaign the player is playing in --- specifically, the
		name from the corresponding world descriptor.  Currently
		this will be one of "Maj'Eyal", "Infinite", "Arena" or
		"Orcs"; more possibilities may be added by various DLC or
		addons.

	  summoned:
		Present if the actor being planned for is a sidekick of the
		player (for instance, the Alchemist's golem).  If possible,
		this will be set to the name of the field in the player
		that contains this sidekick, by which you can distinguish
		between, for instance, the Alchemist's golem ("alchemy_golem")
		and the Writhing One's worm that walks ("demented_wtw").
		If that can't be determined, the field will simply be true.

	Your addon can set additional flags via the "TalentPointPlanner:flags"
	hook described above.


Optional fields and methods
---------------------------

Your source definitions can also include the following optional fields and
methods:

  use_post(p, def, cfg):
	Applies additional changes to the player clone 'p' to simulate the
	talent source's bonuses.  This method is called (if present) after
	the player clone has been force-leveled to level 50 (or other
	target level set by the user).  See, for instance, the planner's
	handling of the Legacy of the Naloren prodigy.

  show_if_done:
	If set, the planner will list this talent source after it's been
	received [done() returns true].  You should set this if your
	source's available() or known() method will return false after the
	source has been gained.

	For instance, if you're writing a source definition for your
	addons's new gainable Technique / Hit Things talent category, your
	available() method might return false for players that already know
	that category.  Normally, that would prevent the "You already have
	Technique / Hit Things from Punch Trainer" line from showing on the
	planner dialog after the player has gained the category, since it
	generally only lists sources for which available() and known() both
	return true.  The 'show_if_done' field overrides that.

	The field value can be either true or a function(d, def) that
	should return true if the source should be listed if iit has
	been gained.

  only_one:
  	Sometimes you have talent sources that are mutually exclusive, in
	that the player can only receive one of them — for instance, the
	three talent sources for the Technomancer evolution, depending on
	which tree you want to have unlocked.  If you have talent sources
	like that, you can tell that to the planner by setting this field
	to the same value in all of the relevant sources.  This works
	roughly like a radio button group; the planner dialog will enforce
	that at most one member of the group can be selected, and they even
	get a special radio-button-style rendering.  Once the player has
	received one of these sources and its done() method returns true,
	the planner dialog will treat every other source in the group as
	though its available() method returns false.

  golem_msg:
	A string tag selecting one of a few boilerplate golem-related
	warning messages for sources that would be of limited use to the
	Alchemist's golem.  Used to present a tooltip for the source when
	leveling up the golem.  Supported tags are:

	  generic_pts	For sources that provide generic talent points.

	  generic_tree	For sources that provide a locked generic tree.

	  cat_point	For sources that provide a category point.

  tooltip or tooltip(d, def, cfg):
	A string, or method returning a string, to be used as a tooltip for
	this talent source.  Superseded by 'golem_msg' if both are present.
	If the method returns nil, no tooltip will be shown.

  dialogable:
	You should usually set this to true if your known() method calls
	d:hasSeen().  See the ":hasSeen() and :hasDone()" section below for
	more information.

  config_dialog:
	If your talent source needs extra configuration information from
	the player, you can use this field to have the planner pop up a
	dialog for the player to enter it.  See the "Configuration dialog"
	section below for more details.


Planner dialog utility methods
------------------------------

In addition to the data fields described above, the planner dialog also has
some utility methods that can be helpful in your source methods:

  d:hasWorldAchievement(tag)
	Returns true if the specified achievement has been unlocked by any
	character at any difficulty or permadeath setting.

  d:hasAchievement(tag)
	Returns true if the current actor (or the player, if those are
	different) has unlocked the specified achievement.

  d:doneQuest(tag, subgoal, or_done)
	Returns true if the player has completed the specified quest, or
	the subgoal thereof if specified.  If 'or_done' is true, also
	returns true if the quest is successfully finished (ie. in the
	state Quest.DONE).

  d:isSpellcaster()
	Returns true if the current actor is a spellcaster.  This roughly
	duplicates the check used by the game to decide whether Zigur
	should be placed on the map.

  d:hasSeen(tag)
	Returns true if :rememberSeen() has been called with the specified
	tag for any character.  See the ":hasSeen() and :hasDone()" section
	below for more details.

  d:hasDone(tag)
	Returns true if :rememberDone() has been called with the specified
	tag for the current character.  See the ":hasSeen() and :hasDone()"
	section below for more details.

  d:registerMarkable(tag)
	Adds the provided tag as one that the Mark Talent Source as Seen
	dialog (described in the ":hasSeen() and :hasDone()" section below)
	should accept.  If your tag is the same as the tag of one of your
	talent sources, you should instead set 'dialogable=true' in that
	source's definition; this method is for when that isn't sufficient.


:hasSeen() and :hasDone()
-------------------------

Sometimes your known() and done() methods can use already available
information to determine their return values.  For instance, the talent
source for the Arena's generic talent points reward only needs to call
"d:hasWorldAchievement('THE_ARENA')" from its known() method and
"d:hasAchievement('THE_ARENA')" from its done() method.  Sometimes, alas,
it's not that easy; if there's no achievement or quest information that can
indicate whether the player has seen or gained your talent source, you may
have to tell the planner explicitly.

At the point where the player can be considered to "know" about your talent
source, you can add code like the following:

	local Planner = require_first('mod.dialogs.PointPlanDialog')
	if Planner then Planner:rememberSeen("source_tag") end

Usually you'll want to use the identifying tag you specified for your
talent source [though sometimes not; see the :registerMarkable() method
described below].  Once ths code has been invoked, d:hasSeen("source_tag")
will return true for this character and all subsequent characters.

Similarly, at the point where you give the talent source's bonuses to the
player, you can add code like the following:

	local Planner = require_first('mod.dialogs.PointPlanDialog')
	if Planner then Planner:rememberDone("source_tag") end

Once this code has been invoked, d:hasDone("source_tag") will return true
for the current character.

Sometimes the point at which a talent source should be considered "seen" or
"done" is inside a chat dialog, or after using an object.  :rememberSeen()
and :rememberDone() can, of course, be called from the relevant .action()s
in the chat or from the object's .use() method, if you're the one writing
them; if you're providing talent sources for someone else's addon, though,
trying to modify these methods in place to add callbacks can be fraught and
fragile (ask me how I know...).  To help avoid that necessity, we provide
two handy hooks, described in the "Main entry hooks" section above:
TalentPointPlanner:chatAnswer, which is triggered just before the usual
engine.dialogs.Chat:use() handling when the user choose an answer in a chat
stage, and TalentPointPlanner:playerUseObject, which is triggered just
after the usual mod.class.Player:playerUseObject() handling.  You can see
examples of using both hooks in this addon's hooks/load.lua.

Also of interest is the Mark Talent Source as Seen dialog, accessible via
the Mark as Seen button on the main planner dialog.  This is useful if the
player knows about your talent source but doesn't "officially" know,
because they haven't gone through the appropriate in-game motions (maybe
they did so with an earlier character, for instance, before installing the
Talent Point Planner addon).  If you set "dialogable = true" in your talent
source definition, the player can enter your source's tag in the Mark as
Seen dialog and the dialog will call :rememberSeen() for your source.

It's sometimes useful for multiple talent sources to share a single "seen"
tag (if they're listed in the same chat dialog conferring them, for
instance).  In that case, you may want to use a "seen" tag that doesn't
match the tag of any of your talent sources.  You will need to tell the
planner about your "shared" seen tag, so that the Mark Talent Source as
Seen dialog will recognize it; to do that, include the following code in
your TalentPointPlanner:talentSource hook callback:

	data.dialog:registerMarkable("shared_tag")


Configuration dialog
--------------------

Sometimes your talent source needs more information to determine how to
apply itself to the planner dialog's player clone.  For instance, v1.6's
handling of Lichform needed to know how many points the player planned to
invest in Lichform and whether the player planned to spend an extra
category point in Spell/Necrosis before becoming a lich.  If your talent
source needs information like that, you can arrange for the planner dialog
to pop up a dialog to collect it.

The 'config_dialog' field in your talent source definition can be set to
the fully qualified class name of your dialog class, suitable for passing
to require().  Your dialog's :init() method will receive three arguments:

  actor		The actor we're building a plan for.

  cur_cfg	The configuration information that the player entered into
		this dialog last time, or an empty table.

  callback	A callback method that your dialog should call with the new
		configuration information if the player sets it, or with
		nil if the player cancels out of your dialog.

The configuration information entered by the player will be passed to
various talent source methods defined above as the 'cfg' argument.  For a
reference implementation, you can look at the (deprecated) LichFormDialog
defined in Talent Point Planner.

You may also include a .config_dump() method in your talent source
definition, to describe the current configuration information for this
talent source in the text file dump (bound to <Ctrl-U> in the planner
levelup dialog).  The method will receive three arguments:

  dlg		The planner dialog
  def		The talent source definition
  cfg		The current configuration information

and should return either a single string or a list of strings.


($Id: hooks.txt 5033 2024-10-14 23:26:17Z dsb $)
