diff --git a/.gitignore b/.gitignore
index 6fe7df2d..867d5fe7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -330,3 +330,51 @@ ASALocalRun/
# MFractors (Xamarin productivity tool) working folder
.mfractor/
+
+# LaTeX
+*.acn
+*.acr
+*.alg
+*.aux
+*.bak
+*.bbl
+*.bcf
+*.blg
+*.brf
+*.bst
+*.dvi
+*.fdb_latexmk
+*.fls
+*.glg
+*.glo
+*.gls
+*.idx
+*.ilg
+*.ind
+*.ist
+*.lof
+*.log
+*.lol
+*.lot
+*.maf
+*.mtc
+*.mtc1
+*.nav
+*.nlo
+*.nls
+*.out
+*.pdf
+*.pyg
+*.run.xml
+*.snm
+*.synctex.gz
+*.tex.backup
+*.tex~
+*.thm
+*.toc
+*.vrb
+*.xdy
+*.xml
+*blx.bib
+.bak
+.mtc
diff --git a/ApiDocs/en/apiReference.tex b/ApiDocs/en/apiReference.tex
new file mode 100644
index 00000000..38147006
--- /dev/null
+++ b/ApiDocs/en/apiReference.tex
@@ -0,0 +1,7 @@
+\section{API reference}
+
+\input{standaloneFuncs.tex}
+\newpage
+\input{enumerations.tex}
+\newpage
+\input{types.tex}
diff --git a/ApiDocs/en/attack.tex b/ApiDocs/en/attack.tex
new file mode 100644
index 00000000..75571b11
--- /dev/null
+++ b/ApiDocs/en/attack.tex
@@ -0,0 +1,49 @@
+\subsection{Attack}
+\label{Attack}
+Represents attack of \hyperref[UnitImpl]{unit implementation}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns attack \hyperref[Id]{id}. This is different for every \hyperref[DynamicUpgrade]{dynamic upgrade} unit gets\\
+\hline
+type & Returns attack \hyperref[AttackCategory]{type}\\
+\hline
+source & Returns attack \hyperref[SourceCategory]{source}\\
+\hline
+reach & Returns attack \hyperref[ReachCategory]{reach}\\
+\hline
+initiative & Returns attack initiative value\\
+\hline
+power & Returns attack power (accuracy)\\
+\hline
+damage & Returns damage the attack can inflict. Damage depends on attack type\\
+\hline
+heal & Returns healing the attack can apply. Healing depends on attack type\\
+\hline
+infinite & Returns true if attack has long effect duration. Effect depends on attack type\\
+\hline
+crit & Returns true if attack can inflict critical damage\\
+\hline
+level & Returns level for boost damage, lower damage and lower initiative attacks\\
+\hline
+melee & Returns true if attack is melee (\texttt{L\_ADJACENT} or custom reach marked as \texttt{MELEE} in \texttt{LAttR.dbf})\\
+\hline
+maxTargets & Returns maximum number of targets (1, 6 or \texttt{MAX\_TARGTS} value for custom reach in \texttt{LAttR.dbf})\\
+\hline
+critDamage & Returns critical damage percent \texttt{[0 : 255]}\\
+\hline
+critPower & Returns critical damage chance \texttt{[0 : 100]}\\
+\hline
+damageRatio & Returns damage ratio \texttt{[0 : 255]} for additional targets\\
+\hline
+damageRatioPerTarget & Returns true if damage ratio reapplied for each consecutive target\\
+\hline
+damageSplit & Returns true if damage is split among targets\\
+\hline
+wards & Returns array of \hyperref[Modifier]{modifiers} applied by bestow wards attack\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/battle.tex b/ApiDocs/en/battle.tex
new file mode 100644
index 00000000..ec3c3051
--- /dev/null
+++ b/ApiDocs/en/battle.tex
@@ -0,0 +1,87 @@
+\subsection{Battle}
+\label{Battle}
+Represents battle state.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+currentRound & Returns current round in battle. Round counting starts from 1, but there is a special round 0 when units with `Doppelganger' \hyperref[Attack]{attacks} present\\
+\hline
+autoBattle & Returns \texttt{true} if autobattle mode is turned on\\
+\hline
+fastBattle & Returns \texttt{true} if fast battle is turned on. When fast battle is active, auto battle is active too\\
+\hline
+attackerPlayer & Returns \hyperref[Player]{player} that started battle or \texttt{nil} if not found\\
+\hline
+defenderPlayer & Returns \hyperref[Player]{player} that was attacked or \texttt{nil} if not found\\
+\hline
+attacker & Returns \hyperref[Stack]{stack} that started battle. Only stacks can initiate battles\\
+\hline
+defender & Returns \hyperref[Group]{group} that was attacked. Defender group can represent units of a \hyperref[Stack]{stack}, \hyperref[Fort]{fort} or \hyperref[Ruin]{ruin}. Use \texttt{group.id.type} to get an actual type of a group\\
+\hline
+decidedToRetreat & Returns \texttt{true} if decision about groups retreat was made and should not be reconsidered\\
+\hline
+afterBattle & Returns \texttt{true} if battle is over but healers can make one more turn to heal allies\\
+\hline
+duel & Returns \texttt{true} if battle is a duel between thief and a stack leader. All units except leaders are marked with \texttt{Hidden} \hyperref[BattleStatus]{battle status}\\
+\hline
+turnsOrder & Returns list of \hyperref[BattleTurn]{battle turns} remaining in the current round of battle. Position of elements in the list corresponds to order of remaining turns in current round. In other words, at the start of a round, battle turns in the list are sorted according to units initiative, including an additional random initiative\\
+\hline
+\end{tabularx}
+\end{center}
+
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+getUnitStatus(id, BattleStatus.Defend) & Returns \texttt{true} if a unit with a specified \hyperref[Id]{id} has a specified \hyperref[BattleStatus]{battle status}\\
+\hline
+isUnitAttacker(unit) & Returns \texttt{true} if \hyperref[Unit]{unit} belongs to attacker group.\\
+isUnitAttacker(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getUnitActions(unit) & Returns possible actions and attack options for specified \hyperref[Unit]{unit}.\\
+getUnitActions(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getRetreatStatus(true) & Returns retreat status of attacker (\texttt{true}) or defender (\texttt{false}) group\\
+getRetreatStatus(false) &\\
+\hline
+isUnitRevived(unit) & Returns \texttt{true} if specified \hyperref[Unit]{unit} was revived during battle.\\
+isUnitRevived(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+isUnitWaiting(unit) & Returns \texttt{true} if specified \hyperref[Unit]{unit} skipped its turn and waiting.\\
+isUnitWaiting(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getUnitShatteredArmor(unit) & Returns amount of \hyperref[Unit]{unit} armor shattered in battle so far.\\
+getUnitShatteredArmor(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getUnitFortificationArmor(unit) & Returns armor that is granted to \hyperref[Unit]{unit} by fortification, if any.\\
+getUnitFortificationArmor(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+isUnitResistantToClass(unit, attackClass) & Returns true if specified \hyperref[Unit]{unit} is resistant to \hyperref[AttackCategory]{attack class}.\\
+isUnitResistantToClass(id, attackClass) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+isUnitResistantToSource(unit, attackSource) & Returns true if specified \hyperref[Unit]{unit} is resistant to \hyperref[SourceCategory]{attack source}.\\
+isUnitResistantToSource(id, attackSource) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getUnitDisableRound(unit) & Returns round when paralyze, petrify or fear was applied to \hyperref[Unit]{unit}. Returns 0 if unit is not disabled.\\
+getUnitDisableRound(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getUnitPoisonRound(unit) & Returns round when long poison was applied to \hyperref[Unit]{unit}. Returns 0 if unit is not poisoned.\\
+getUnitPoisonRound(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getUnitFrostbiteRound(unit) & Returns round when long frostbite was applied to \hyperref[Unit]{unit}. Returns 0 if unit is not frozen.\\
+getUnitFrostbiteRound(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getUnitBlisterRound(unit) & Returns round when long blister was applied to \hyperref[Unit]{unit}. Returns 0 if unit is not burning.\\
+getUnitBlisterRound(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+getUnitTransformRound(unit) & Returns round when long transform was applied to \hyperref[Unit]{unit}. Returns 0 if unit is not transformed.\\
+getUnitTransformRound(id) & Method also accepts unit \hyperref[Id]{ids}\\
+\hline
+setRetreatStatus(true, Retreat.FullRetreat) & Sets \hyperref[Retreat]{retreat status} of attacker (\texttt{true}) or defender (\texttt{false}) group. This method can be only used in AI battle action script\\
+setRetreatStatus(false, Retreat.NoRetreat) &\\
+\hline
+setDecidedToRetreat() & Notifies battle state that the decision about groups retreat was made and it is final. This method can be only used in AI battle action script\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/battleturn.tex b/ApiDocs/en/battleturn.tex
new file mode 100644
index 00000000..5b1d891b
--- /dev/null
+++ b/ApiDocs/en/battleturn.tex
@@ -0,0 +1,15 @@
+\subsection{Battle turn}
+\label{BattleTurn}
+Represents \hyperref[Unit]{unit} action inside battle round\\
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+unit & Returns \hyperref[Unit]{unit} that performs the turn\\
+\hline
+attackCount & Returns number of attacks unit can perform in its turn. Units with double attack (\texttt{turn.unit.impl.attacksTwice}) will have 2 in attackCount\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/building.tex b/ApiDocs/en/building.tex
new file mode 100644
index 00000000..4a523b6d
--- /dev/null
+++ b/ApiDocs/en/building.tex
@@ -0,0 +1,18 @@
+\subsection{Building}
+\label{Building}
+Represents building in \hyperref[Player]{player's} capital.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns building \hyperref[Id]{id}. \texttt{BUILD\_ID} value from \texttt{GBuild.dbf}\\
+cost & Returns building \hyperref[Currency]{cost}. \texttt{COST} value from \texttt{GBuild.dbf}\\
+type & Returns building \hyperref[BuildingCategory]{type}. \texttt{CATEGORY} value from \texttt{GBuild.dbf}\\
+requiredBuilding & Returns \hyperref[Building]{building} that is required to build this one or \texttt{nil} if building has no requirements. \texttt{REQUIRED} value from \texttt{GBuild.dbf}\\
+branch & Returns unit \hyperref[UnitBranch]{branch} or -1 if building type is not \texttt{Unit}\\
+level & Returns building level or 0 if building type is not \texttt{Unit}\\
+\hline
+\end{tabularx}
+\end{center}
diff --git a/ApiDocs/en/crystal.tex b/ApiDocs/en/crystal.tex
new file mode 100644
index 00000000..5fbe248f
--- /dev/null
+++ b/ApiDocs/en/crystal.tex
@@ -0,0 +1,17 @@
+\subsection{Crystal}
+\label{Crystal}
+Represents gold mine or mana source on a map.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns crystal \hyperref[Id]{id}. The value is unique for every crystal on scenario map\\
+\hline
+position & Returns crystal position as a \hyperref[Point]{point}\\
+\hline
+resource & Returns crystal \hyperref[Resource]{resource type}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/currency.tex b/ApiDocs/en/currency.tex
new file mode 100644
index 00000000..bb11da09
--- /dev/null
+++ b/ApiDocs/en/currency.tex
@@ -0,0 +1,34 @@
+\subsection{Currency}
+\label{Currency}
+Represents game currency, mana and gold united.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+gold & Returns or sets amount of gold\\
+\hline
+infernalMana & Returns or sets amount of infernal mana\\
+\hline
+lifeMana & Returns or sets amount of life mana\\
+\hline
+deathMana & Returns or sets amount of death mana\\
+\hline
+runicMana & Returns or sets amount of runic mana\\
+\hline
+groveMana & Returns or sets amount of grove mana\\
+\hline
+\end{tabularx}
+\end{center}
+
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Currency.new(existing) & Creates new currency from existing object\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/customModifiersExample.tex b/ApiDocs/en/customModifiersExample.tex
new file mode 100644
index 00000000..24b3ec7d
--- /dev/null
+++ b/ApiDocs/en/customModifiersExample.tex
@@ -0,0 +1,88 @@
+\subsection{Custom modifiers}
+Modifiers in the game stack on top of each other, making ordered modifiers chain.
+First modifier takes base values from the unit it modifies, next - values modified by the first modifier and so on.
+Custom modifiers allow you to modify every stat of unit and its attacks in a single script file with a number of uniform functions.\\
+Most of the custom modifier functions have the following form:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getSomething(unit, prev)
+ if someCondition then
+ return modifiedValue
+ end
+
+ return prev
+end
+\end{lstlisting}
+\end{center}
+\begin{itemize}
+\item \texttt{unit} has type \hyperref[Unit]{Unit}. The unit is presented in a state before the current modifier is applied.
+\item \texttt{prev} is a previous value of the stat. It is either a base value or a value modified by the previous modifier.
+\end{itemize}
+Check \href{https://github.com/VladimirMakeev/D2ModdingToolset/tree/master/Scripts/Modifiers}{Scripts/Modifiers} for script examples.\\
+\href{https://github.com/VladimirMakeev/D2ModdingToolset/blob/master/Scripts/Modifiers/template.lua}{template.lua} contains a complete list of available functions.\\
+
+\textbf{Due to how modifiers chain work, you have no direct access to final unit stats}\\
+For example:
+\begin{itemize}
+\item Lets say we have a unit with base of 50 initiative;
+\item Then we give it a potion that increases damage by 50\% \textbf{if unit initiative} \texttt{> 60};
+\item Then we give it another potion that increases initiative to 70.
+\end{itemize}
+The damage bonus \textbf{will not work} in this case, even though a final unit initiative is 70 that is \texttt{> 60}. This is because \textbf{damage modifier applied earlier than initiative modifier}.
+If we get \texttt{unit.impl.attack1.initiative} from the damage modifier script it will be 50, because \textbf{initiative modifier is not applied yet}.\\
+
+\textbf{Dangerous way around to get final unit stats}\\
+The limitation described above \textbf{can be avoided} by getting unit instance via \texttt{getScenario:getUnit(unit.id)}.\\
+But you have to be \textbf{very careful} using this approach, as you can easily fall into a deadloop.\\
+
+Lets see a \textbf{bad example} where we created a modifier that grants bonus armor and regen depending on each other:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getArmor(unit, prev)
+ local finalUnit = getScenario():getUnit(unit.id)
+ return prev + finalUnit.impl.regen / 5
+end
+
+function getRegen(unit, prev)
+ local finalUnit = getScenario():getUnit(unit.id)
+ return prev + finalUnit.impl.armor / 10
+end
+\end{lstlisting}
+\end{center}
+Or it can be two different modifiers, does not matter:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+-- MyBonusArmorMod.lua
+function getArmor(unit, prev)
+ local finalUnit = getScenario():getUnit(unit.id)
+ return prev + finalUnit.impl.regen / 5
+end
+\end{lstlisting}
+\end{center}
+When this modifier(s) applied to a unit, we are getting circular dependence here: \textbf{final armor depends on final regen while final regen depends on final armor}.\\
+Imagine what happens when the game tries to get unit armor:\\
+It calls \texttt{getArmor} that calls \texttt{getRegen} that calls \texttt{getArmor} that calls \texttt{getRegen} that calls \texttt{getArmor} that calls \texttt{getRegen} that calls \texttt{getArmor}... and so on until your \textbf{game hang or crash to desktop}.\\
+
+As a \textbf{good example}, you could refer to a third stat, thus avoiding deadloop condition:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+-- MyBonusArmorMod.lua
+function getArmor(unit, prev)
+ local finalUnit = getScenario():getUnit(unit.id)
+ return prev + finalUnit.impl.regen / 5
+end
+\end{lstlisting}
+\end{center}
+and
+\begin{center}
+\begin{lstlisting}[language=Lua]
+-- MyBonusRegenMod.lua
+function getRegen(unit, prev)
+ local finalUnit = getScenario():getUnit(unit.id)
+ return prev + finalUnit.impl.level / 10
+end
+\end{lstlisting}
+\end{center}
+This way, regen depends on level, and armor depends on regen and there is no circular dependence in this case.\\
+
+\textbf{Remember that this is subject for all modifiers that can potentially happen to be applied to the same unit}.
\ No newline at end of file
diff --git a/ApiDocs/en/customReachExample.tex b/ApiDocs/en/customReachExample.tex
new file mode 100644
index 00000000..58f75e74
--- /dev/null
+++ b/ApiDocs/en/customReachExample.tex
@@ -0,0 +1,35 @@
+\subsection{Targeting scripts (custom attack reaches, specified via LAttR.dbf)}
+Targeting scripts are used to specify either selection or attack targets of custom attack reach:
+\begin{itemize}
+\item \textbf{Selection} targets are targets that can be \textbf{selected (clicked)} (specified as \texttt{SEL\_SCRIPT} in \texttt{LAttR.dbf});
+\item \textbf{Attack} targets are targets that will be \textbf{affected by attack} (specified as \texttt{ATT\_SCRIPT} in \texttt{LAttR.dbf}). For instance, in case of 'pierce` attack, you can only click adjacent targets, but the attack will not only affect the selected target but also the one behind it (if any). Thus the 'pierce` attack uses \texttt{getAdjacentTargets.lua} \textbf{as selection} script and \texttt{getSelectedTargetAndOneBehindIt.lua} as attack script.
+\end{itemize}
+Targeting scripts use uniform \texttt{getTargets} function for both selection and attack scripts with the following arguments:
+\begin{itemize}
+\item \texttt{attacker} is the \hyperref[UnitSlot]{unit slot} of the attacker unit;
+\item \texttt{selected} is the \hyperref[UnitSlot]{unit slot} of the unit that was selected (clicked).\\
+\texttt{selected.position == -1} and \texttt{selected.unit == nil} if this is a selection script (no target is clicked yet);
+\item \texttt{allies} are \hyperref[UnitSlot]{unit slots} of all the allies on the battlefield (excluding the attacker);
+\item \texttt{targets} are \hyperref[UnitSlot]{unit slots} of all the targets on the battlefield on which the attack can be performed. For instance, if targets are allies and the attack is Revive, then it will only include dead allies that can be revived;
+\item \texttt{targetsAreAllies} specified whether targets are allies;
+\item \texttt{item} specifies an \hyperref[Item]{item} (orb or talisman) used to perform the attack, or \texttt{nil} if no item is used;
+\item \texttt{battle} specifies an information about current \hyperref[Battle]{battle};
+\item \texttt{isMarking} specified whether the script is being called to mark targets visually on the battlefield. Can be used to provide consistent visual representation for randomized scripts, as soft alternative to \texttt{MRK\_TARGTS} flag in \texttt{LAttR.dbf}. Always \texttt{false} if this is a selection script.
+\end{itemize}
+Example of attack script of pierce attack (\texttt{getSelectedTargetAndOneBehindIt.lua}):
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getTargets(attacker, selected, allies, targets, targetsAreAllies, item, battle, isMarking)
+ -- Get the selected target and the one behind it (pierce attack)
+ local result = {selected}
+ for i = 1, #targets do
+ local target = targets[i]
+ if target.backline and target.position == selected.position + 1 then
+ table.insert(result, target)
+ break
+ end
+ end
+ return result
+end
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/diplomacy.tex b/ApiDocs/en/diplomacy.tex
new file mode 100644
index 00000000..b78bfceb
--- /dev/null
+++ b/ApiDocs/en/diplomacy.tex
@@ -0,0 +1,31 @@
+\subsection{Diplomacy}
+\label{Diplomacy}
+Represents diplomacy relations between \hyperref[RaceCategory]{races} in \hyperref[Scenario]{scenario}.
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+getCurrentRelation(race1, race2) & Returns current diplomacy relations value between two \hyperref[RaceCategory]{races} in range \texttt{[0 : 100]}\\
+\hline
+getPreviousRelation(race1, race2) & Returns previous diplomacy relations value between two \hyperref[RaceCategory]{races} in range \texttt{[0 : 100]}\\
+\hline
+getAlliance(race1, race2) & Returns \texttt{true} if two \hyperref[RaceCategory]{races} are in alliance\\
+\hline
+getAllianceTurn(race1, race2) & Returns turn number when two \hyperref[RaceCategory]{races} made an alliance. Returns zero if races are not in alliance\\
+\hline
+getAlwaysAtWar(race1, race2) & Returns \texttt{true} if two \hyperref[RaceCategory]{races} are always at war\\
+\hline
+getAiCouldNotBreakAlliance(race1, race2) & Returns \texttt{true} if diplomacy relations prohibit AI-controlled \hyperref[RaceCategory]{races} from breaking alliance\\
+\hline
+getRelationType(value) & Returns \hyperref[Relation]{relation type} according to diplomacy relations value\\
+\hline
+\end{tabularx}
+\end{center}
+\paragraph{Relations value to type mapping}
+\begin{itemize}
+\item \texttt{[0 : D\_WAR]} - \texttt{Relation.War}
+\item \texttt{[D\_WAR : D\_NEUTRAL]} - \texttt{Relation.Neutral}
+\item \texttt{[D\_NEUTRAL : 100]} - \texttt{Relation.Peace}
+\end{itemize}
diff --git a/ApiDocs/en/docs.tex b/ApiDocs/en/docs.tex
new file mode 100644
index 00000000..616bf217
--- /dev/null
+++ b/ApiDocs/en/docs.tex
@@ -0,0 +1,102 @@
+\documentclass[10pt]{article}
+\usepackage[T1]{fontenc}
+\usepackage[utf8]{inputenc}
+\usepackage[english]{babel}
+%\usepackage{xcolor}
+\usepackage{listings}
+\usepackage{hyperref}
+\usepackage{float}
+\usepackage{fancyhdr}
+\usepackage{graphicx}
+\usepackage{textcmds}
+\usepackage[table]{xcolor}
+\usepackage{tabularx}
+
+\definecolor{dkgreen}{rgb}{0,0.6,0}
+\definecolor{lghtgray}{rgb}{0.97,0.97,0.97}
+\definecolor{lghtgray2}{rgb}{0.94,0.94,0.94}
+\definecolor{gray}{rgb}{0.5,0.5,0.5}
+\definecolor{kwblue}{rgb}{0.2,0.6,1.0}
+
+\lstdefinestyle{lua}{
+ defaultdialect=[5.0]Lua,
+ deletekeywords={type},
+ frame=single,
+ language=Lua,
+ aboveskip=3mm,
+ belowskip=3mm,
+ showstringspaces=false,
+ columns=flexible,
+ backgroundcolor=\color{lghtgray},
+ basicstyle={\small\ttfamily},
+ numbers=left,
+ numberstyle=\footnotesize\color{black},
+ keywordstyle=\color{kwblue}\bfseries,
+ commentstyle=\color{dkgreen},
+ stringstyle=\color{brown},
+ breaklines=true,
+ breakatwhitespace=true,
+ tabsize=4,
+ extendedchars=false,
+ inputencoding=utf8
+}
+
+\lstset{style=lua}
+
+\usepackage{geometry}
+ \geometry{
+ a4paper,
+ total={170mm,257mm},
+ left=20mm,
+ top=20mm,
+ }
+
+\usepackage{hyperref}
+\hypersetup{
+ colorlinks=true,
+ linkcolor=teal,
+ urlcolor=blue,
+ }
+
+
+% In case you want watermark on each page
+%\usepackage{draftwatermark}
+%\SetWatermarkText{Draft}
+%\SetWatermarkScale{2}
+%\SetWatermarkColor{red}
+
+
+\author{mak}
+\title{Lua API for D2 modding toolset\\Documentation for version 0.15}
+
+\begin{document}
+\pagestyle{fancy}
+ % clear all header fields
+\fancyhead{}
+ % clear all footer fields
+\fancyfoot{}
+% page number in the center for even and odd pages
+\fancyfoot[CE,CO]{\thepage}
+\fancyhead[RO,RE]{\textcolor{gray}{D2 modding toolset 0.15}}
+
+\maketitle
+\newpage
+\tableofcontents
+\newpage
+\listoffigures
+
+% Overview
+\include{overview.tex}
+% API reference
+\include{apiReference.tex}
+% Examples
+\include{examples.tex}
+% Targeting scripts
+%\include{targetingScripts.tex}
+% Event condition examples
+%\include{eventConditions.tex}
+% Custom modifiers
+%\include{customModifiers.tex}
+
+
+\end{document}
\ No newline at end of file
diff --git a/ApiDocs/en/doppelgangerExample.tex b/ApiDocs/en/doppelgangerExample.tex
new file mode 100644
index 00000000..36365912
--- /dev/null
+++ b/ApiDocs/en/doppelgangerExample.tex
@@ -0,0 +1,23 @@
+\subsection{doppelganger.lua}
+\texttt{doppelganger} and \texttt{target} have type \hyperref[Unit]{Unit}. \texttt{item} is \hyperref[Item]{Item} used to perform the attack. \texttt{battle} specifies information about \hyperref[Battle]{battle} state.
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getLevel(doppelganger, target, item, battle)
+ -- Get current doppelganger implementation
+ local impl = doppelganger.impl
+ -- Get target unit implementation
+ local targImpl = target.impl
+
+ -- Get least level value from both
+ local level = math.min(impl.level, targImpl.level)
+
+ -- Make sure doppelganger transform level is not lesser than target's base
+ local baseImpl = target.baseImpl
+ if level < baseImpl.level then
+ level = baseImpl.level
+ end
+
+ return level
+end
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/drainLevelExample.tex b/ApiDocs/en/drainLevelExample.tex
new file mode 100644
index 00000000..f06ddb71
--- /dev/null
+++ b/ApiDocs/en/drainLevelExample.tex
@@ -0,0 +1,10 @@
+\subsection{drainLevel.lua}
+\texttt{attacker} and \texttt{target} have type \hyperref[Unit]{Unit}. \texttt{item} is \hyperref[Item]{Item} used to perform the attack. \texttt{battle} specifies information about \hyperref[Battle]{battle} state.
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getLevel(attacker, target, item, battle)
+ -- transform into unit with its level minus 1 and minus attacker over-level
+ return math.max(1, target.impl.level - 1 - attacker.impl.level + attacker.baseImpl.level);
+end
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/dynamicupgrade.tex b/ApiDocs/en/dynamicupgrade.tex
new file mode 100644
index 00000000..3c934a2c
--- /dev/null
+++ b/ApiDocs/en/dynamicupgrade.tex
@@ -0,0 +1,13 @@
+\subsection{Dynamic upgrade}
+\label{DynamicUpgrade}
+Represents rules that applied when unit makes its progress gaining levels. Records in \texttt{GDynUpgr.dbf} are dynamic upgrades.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+xpNext & Returns number of experience points added with each dynamic upgrade. \texttt{XP\_NEXT} value from \texttt{GDynUpgr.dbf}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/enumerations.tex b/ApiDocs/en/enumerations.tex
new file mode 100644
index 00000000..ce58663a
--- /dev/null
+++ b/ApiDocs/en/enumerations.tex
@@ -0,0 +1,773 @@
+\subsection{Enumerations}
+\subsubsection{Race}
+\label{RaceCategory}
+Race categories correspond to the contents of \texttt{LRace.dbf} and can be accessed using table \texttt{Race}.
+By default, table contains following races:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Race.Human & Empire\\
+\hline
+Race.Undead & Undead Hordes\\
+\hline
+Race.Heretic & Legions of the Damned\\
+\hline
+Race.Dwarf & Mountain Clans\\
+\hline
+Race.Neutral & Neutrals\\
+\hline
+Race.Elf & Elven Alliance\\
+\hline
+\end{tabularx}
+
+\subsubsection{Subrace}
+\label{SubraceCategory}
+Subrace categories correspond to the contents of \texttt{LSubRace.dbf} and can be accessed using table \texttt{Subrace}.
+By default, table contains following subraces:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Subrace.Custom &\\
+\hline
+Subrace.Human &\\
+\hline
+Subrace.Undead &\\
+\hline
+Subrace.Heretic &\\
+\hline
+Subrace.Dwarf &\\
+\hline
+Subrace.Neutral &\\
+\hline
+Subrace.NeutralHuman &\\
+\hline
+Subrace.NeutralElf &\\
+\hline
+Subrace.NeutralGreenSkin &\\
+\hline
+Subrace.NeutralDragon &\\
+\hline
+Subrace.NeutralMarsh &\\
+\hline
+Subrace.NeutralWater &\\
+\hline
+Subrace.NeutralBarbarian &\\
+\hline
+Subrace.NeutralWolf &\\
+\hline
+Subrace.Elf &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Lord}
+\label{LordCategory}
+Lord categories correspond to the contents of \texttt{LLord.dbf} and can be accessed using table \texttt{Lord}.
+By default, table contains following lord types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Lord.Mage &\\
+\hline
+Lord.Warrior &\\
+\hline
+Lord.Diplomat &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Terrain}
+\label{TerrainCategory}
+Terrain types correspond to the contents of \texttt{LTerrain.dbf} and can be accessed using table \texttt{Terrain}.
+By default, table contains following terrain types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Terrain.Human & Empire native terrain\\
+\hline
+Terrain.Undead & Undead Hordes native terrain\\
+\hline
+Terrain.Heretic & Legions of the Damned native terrain\\
+\hline
+Terrain.Dwarf & Mountain Clans native terrain\\
+\hline
+Terrain.Neutral & Neutrals native terrain\\
+\hline
+Terrain.Elf & Elven Alliance native terrain\\
+\hline
+\end{tabularx}
+
+\subsubsection{Ground}
+\label{GroundCategory}
+Ground types correspond to the contents of \texttt{LGround.dbf} and can be accessed using table \texttt{Ground}.
+By default, table contains following ground types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Ground.Plain &\\
+\hline
+Ground.Forest &\\
+\hline
+Ground.Water &\\
+\hline
+Ground.Mountain & Special type, indicates there is a mountain present on the tile.\\
+\hline
+\end{tabularx}
+
+\subsubsection{Unit}
+\label{UnitCategory}
+Unit types correspond to the contents of \texttt{LUnitC.dbf} and can be accessed using table \texttt{Unit}.
+By default, table contains following unit types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Unit.Soldier & Ordinary units that can be hired in cities or mercenary camps\\
+\hline
+Unit.Noble & Nobles (thieves) units\\
+\hline
+Unit.Leader & Stack leader units\\
+\hline
+Unit.Summon & Units summoned on the strategic map. Living Armor, for example\\
+\hline
+Unit.Illusion & Illusions summoned on the strategic map\\
+\hline
+Unit.Guardian & Race capital guardian units\\
+\hline
+\end{tabularx}
+
+\subsubsection{Leader}
+\label{LeaderCategory}
+Leader types correspond to the contents of \texttt{LLeadC.dbf} and can be accessed using table \texttt{Leader}.
+By default, table contains following leader types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Leader.Fighter & Leaders with melee (\texttt{L\_ADJACENT}) attacks. Default starting leader for \texttt{Warrior} \hyperref[LordCategory]{lord type}\\
+\hline
+Leader.Explorer & Leaders with ranged (\texttt{L\_ANY}) attacks. Default starting leader for \texttt{Diplomat} \hyperref[LordCategory]{lord type}\\
+\hline
+Leader.Mage & Leaders with ranged (\texttt{L\_ALL}) attacks. Default starting leader for \texttt{Mage} \hyperref[LordCategory]{lord type}\\
+\hline
+Leader.Rod & Leaders with rod placement ability\\
+\hline
+Leader.Noble & Nobles (thieves)\\
+\hline
+\end{tabularx}
+
+\subsubsection{Ability}
+\label{AbilityCategory}
+Leader ability types correspond to the contents of \texttt{LLeadA.dbf} and can be accessed using table \texttt{Ability}.
+By default, table contains following leader abilities:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Ability.Incorruptible &\\
+\hline
+Ability.WeaponMaster &\\
+\hline
+Ability.WandScrollUse &\\
+\hline
+Ability.WeaponArmorUse &\\
+\hline
+Ability.BannerUse &\\
+\hline
+Ability.JewelryUse &\\
+\hline
+Ability.Rod &\\
+\hline
+Ability.OrbUse &\\
+\hline
+Ability.TalismanUse &\\
+\hline
+Ability.TravelItemUse &\\
+\hline
+Ability.CriticalHit &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Attack}
+\label{AttackCategory}
+Attack types correspond to the contents of \texttt{LAttC.dbf} and can be accessed using table \texttt{Attack}.
+By default, table contains following attack types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Attack.Damage &\\
+\hline
+Attack.Drain &\\
+\hline
+Attack.Paralyze &\\
+\hline
+Attack.Heal &\\
+\hline
+Attack.Fear &\\
+\hline
+Attack.BoostDamage &\\
+\hline
+Attack.Petrify &\\
+\hline
+Attack.LowerDamage &\\
+\hline
+Attack.LowerInitiative &\\
+\hline
+Attack.Poison &\\
+\hline
+Attack.Frostbite &\\
+\hline
+Attack.Revive &\\
+\hline
+Attack.DrainOverflow &\\
+\hline
+Attack.Cure &\\
+\hline
+Attack.Summon &\\
+\hline
+Attack.DrainLevel &\\
+\hline
+Attack.GiveAttack &\\
+\hline
+Attack.Doppelganger &\\
+\hline
+Attack.TransformSelf &\\
+\hline
+Attack.TransformOther &\\
+\hline
+Attack.Blister &\\
+\hline
+Attack.BestowWards &\\
+\hline
+Attack.Shatter &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Source}
+\label{SourceCategory}
+Attack sources correspond to the contents of \texttt{LAttS.dbf} and can be accessed using table \texttt{Source}.
+By default, table contains following attack sources:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Source.Weapon &\\
+\hline
+Source.Mind &\\
+\hline
+Source.Life &\\
+\hline
+Source.Death &\\
+\hline
+Source.Fire &\\
+\hline
+Source.Water &\\
+\hline
+Source.Earth &\\
+\hline
+Source.Air &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Reach}
+\label{ReachCategory}
+Attack reach types correspond to the contents of \texttt{LAttR.dbf} and can be accessed using table \texttt{Reach}.
+By default, table contains following attack reach types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Reach.All &\\
+\hline
+Reach.Any &\\
+\hline
+Reach.Adjacent &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Immune}
+\label{ImmuneCategory}
+Immune categories correspond to the contents of \texttt{LImmune.dbf} and can be accessed using table \texttt{Immune}.
+By default, table contains following immune categories:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Immune.NotImmune & Target (unit or stack) is not immune\\
+\hline
+Immune.Once & Target has one-time immunity (resistant)\\
+\hline
+Immune.Always & Target is always immune\\
+\hline
+\end{tabularx}
+
+\subsubsection{Item}
+\label{ItemCategory}
+Item types correspond to the contents of \texttt{LMagItm.dbf} and can be accessed using table \texttt{Item}.
+By default, table contains following item types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Item.Armor &\\
+\hline
+Item.Jewel &\\
+\hline
+Item.Weapon &\\
+\hline
+Item.Banner &\\
+\hline
+Item.PotionBoost &\\
+\hline
+Item.PotionHeal &\\
+\hline
+Item.PotionRevive &\\
+\hline
+Item.PotionPermanent &\\
+\hline
+Item.Scroll &\\
+\hline
+Item.Wand &\\
+\hline
+Item.Valuable &\\
+\hline
+Item.Orb &\\
+\hline
+Item.Talisman &\\
+\hline
+Item.TravelItem &\\
+\hline
+Item.Special &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Equipment}
+\label{EquipmentCategory}
+Leader equipment types can be accessed using table \texttt{Equipment}.
+By default, table contains following equipment types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Equipment.Banner &\\
+\hline
+Equipment.Tome &\\
+\hline
+Equipment.Battle1 &\\
+\hline
+Equipment.Battle2 &\\
+\hline
+Equipment.Artifact1 &\\
+\hline
+Equipment.Artifact2 &\\
+\hline
+Equipment.Boots &\\
+\hline
+\end{tabularx}
+
+\subsubsection{DeathAnimation}
+\label{DeathAnimationCategory}
+Death animation types correspond to the contents of \texttt{LDthAnim.dbf} and can be accessed using table \texttt{DeathAnimation}.
+By default, table contains following animations types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+DeathAnimation.Human &\\
+\hline
+DeathAnimation.Heretic &\\
+\hline
+DeathAnimation.Dwarf &\\
+\hline
+DeathAnimation.Undead &\\
+\hline
+DeathAnimation.Neutral &\\
+\hline
+DeathAnimation.Dragon &\\
+\hline
+DeathAnimation.Ghost &\\
+\hline
+DeathAnimation.Elf &\\
+\hline
+\end{tabularx}
+
+\subsubsection{BattleStatus}
+\label{BattleStatus}
+Battle statuses can be accessed using table \texttt{BattleStatus}.
+By default, table contains following statuses:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+BattleStatus.XpCounted & Unit was killed and its experience points were counted\\
+\hline
+BattleStatus.Dead & Unit dead\\
+\hline
+BattleStatus.Paralyze & Unit paralyzed\\
+\hline
+BattleStatus.Petrify & Unit petrified\\
+\hline
+BattleStatus.DisableLong & Long disable applied (paralyze, petrify or fear)\\
+\hline
+BattleStatus.BoostDamageLvl1 & 1st level damage boost applied (25\% by default)\\
+\hline
+BattleStatus.BoostDamageLvl2 & 2nd level damage boost applied (50\% by default)\\
+\hline
+BattleStatus.BoostDamageLvl3 & 3rd level damage boost applied (75\% by default)\\
+\hline
+BattleStatus.BoostDamageLvl4 & 4th level damage boost applied (100\% by default)\\
+\hline
+BattleStatus.BoostDamageLong & Long damage boost (until battle is over or lower damage applied)\\
+\hline
+BattleStatus.LowerDamageLvl1 & 1st level lower damage applied (50\% by default)\\
+\hline
+BattleStatus.LowerDamageLvl2 & 2nd level lower damage applied (33\% by default)\\
+\hline
+BattleStatus.LowerDamageLong & Long lower damage (until battle is over or removed)\\
+\hline
+BattleStatus.LowerInitiative & Lower initiative applied (50\% by default)\\
+\hline
+BattleStatus.LowerInitiativeLong & Long lower initiative\\
+\hline
+BattleStatus.Poison & Poison DoT attack applied\\
+\hline
+BattleStatus.PoisonLong & Long poison applied\\
+\hline
+BattleStatus.Frostbite & Frostbite DoT attack applied\\
+\hline
+BattleStatus.FrostbiteLong & Long frostbite applied\\
+\hline
+BattleStatus.Blister & Blister DoT attack applied\\
+\hline
+BattleStatus.BlisterLong & Long blister applied\\
+\hline
+BattleStatus.Cured & Cure applied\\
+\hline
+BattleStatus.Transform & Unit transformed by another unit\\
+\hline
+BattleStatus.TransformLong & Long transformation applied by another unit\\
+\hline
+BattleStatus.TransformSelf & Unit transfomed itself\\
+\hline
+BattleStatus.TransformDoppelganger & Doppelganger transformation\\
+\hline
+BattleStatus.TransformDrainLevel & Drain level applied\\
+\hline
+BattleStatus.Summon & Unit was summoned during battle\\
+\hline
+BattleStatus.Retreated & Unit retreated from battle\\
+\hline
+BattleStatus.Retreat & Unit is retreating\\
+\hline
+BattleStatus.Hidden & Unit is hidden. For example, while leader dueling a thief\\
+\hline
+BattleStatus.Defend & Defend was used in this round\\
+\hline
+BattleStatus.Unsummoned & Unsummon effect applied\\
+\hline
+\end{tabularx}
+
+\subsubsection{BattleAction}
+\label{BattleAction}
+Battle action types can be accessed using table \texttt{BattleAction}.
+By default, table contains following battle actions:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+BattleAction.Attack &\\
+\hline
+BattleAction.Skip &\\
+\hline
+BattleAction.Retreat &\\
+\hline
+BattleAction.Wait &\\
+\hline
+BattleAction.Defend &\\
+\hline
+BattleAction.Auto &\\
+\hline
+BattleAction.UseItem &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Retreat}
+\label{Retreat}
+Retreat types can be accessed using table \texttt{Retreat}.
+By default, table contains following retreat decisions:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Retreat.NoRetreat & Group does not retreat from battle\\
+\hline
+Retreat.CoverAndRetreat & Frontline units defend and cover retreating leader and backline units\\
+\hline
+Retreat.FullRetreat & Entire group retreats\\
+\hline
+\end{tabularx}
+
+\subsubsection{Relation}
+\label{Relation}
+Diplomacy relation types can be accessed using table \texttt{Relation}.
+By default, table contains following types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Relation.War &\\
+\hline
+Relation.Neutral &\\
+\hline
+Relation.Peace &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Order}
+\label{Order}
+Stack orders correspond to the contents of \texttt{LOrder.dbf} and can be accessed using table \texttt{Order}.
+By default, table contains following order types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Order.Normal &\\
+\hline
+Order.Stand &\\
+\hline
+Order.Guard &\\
+\hline
+Order.AttackStack &\\
+\hline
+Order.DefendStack &\\
+\hline
+Order.SecureCity &\\
+\hline
+Order.Roam &\\
+\hline
+Order.MoveToLocation &\\
+\hline
+Order.DefendLocation &\\
+\hline
+Order.Bezerk &\\
+\hline
+Order.Assist &\\
+\hline
+Order.Steal &\\
+\hline
+Order.DefendCity &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Resource}
+\label{Resource}
+Resource types correspond to the contents of \texttt{LRes.dbf} and can be accessed using table \texttt{Resource}.
+By default, table contains following resource types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Resource.Gold &\\
+\hline
+Resource.InfernalMana & \texttt{L\_RED}\\
+\hline
+Resource.LifeMana & \texttt{L\_YELLOW}\\
+\hline
+Resource.DeathMana & \texttt{L\_ORANGE}\\
+\hline
+Resource.RunicMana & \texttt{L\_WHITE}\\
+\hline
+Resource.GroveMana & \texttt{L\_BLUE}\\
+\hline
+\end{tabularx}
+
+\subsubsection{IdType}
+\label{IdType}
+Identifier types can be accessed using table \texttt{IdType}.
+By default, table contains following types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+IdType.Empty & Empty id\\
+\hline
+IdType.ApplicationText & Entries of \texttt{TApp.dbf} and \texttt{TAppEdit.dbf}\\
+\hline
+IdType.Building & Entries of \texttt{GBuild.dbf}\\
+\hline
+IdType.Race & Entries of \texttt{GRace.dbf}\\
+\hline
+IdType.Lord & Entries of \texttt{GLord.dbf}\\
+\hline
+IdType.Spell & Entries of \texttt{GSpells.dbf}\\
+\hline
+IdType.UnitGlobal & Unit implementations, entries of \texttt{GUnits.dbf}\\
+\hline
+IdType.UnitGenerated & Runtime-generated unit implementations\\
+\hline
+IdType.UnitModifier & Unit modifiers, entries of \texttt{GModif.dbf}\\
+\hline
+IdType.Attack & Attacks, entries of \texttt{GAttacks.dbf}\\
+\hline
+IdType.TextGlobal & Entries of \texttt{TGlobal.dbf}\\
+\hline
+IdType.LandmarkGlobal & Entries of \texttt{GLmark.dbf}\\
+\hline
+IdType.ItemGlobal & Base items, entries of \texttt{GItem.dbf}\\
+\hline
+IdType.NobleAction & Noble (thief) actions, entries of \texttt{GAction.dbf}\\
+\hline
+IdType.DynamicUpgrade & Dynamic upgrade rules, entries of \texttt{GDynUpgr.dbf}\\
+\hline
+IdType.DynamicAttack & Runtime-generated unit primary attacks\\
+\hline
+IdType.DynamicAltAttack & Runtime-generated unit primary alternative attacks\\
+\hline
+IdType.DynamicAttack2 & Runtime-generated unit secondary attacks\\
+\hline
+IdType.DynamicAltAttack2 & Runtime-generated unit secondary alternative attacks\\
+\hline
+IdType.CampaignFile & Campaign files\\
+\hline
+IdType.Plan & Utility for fast object lookup by map coordinates\\
+\hline
+IdType.ObjectCount & Number of objects in scenario file\\
+\hline
+IdType.ScenarioFile & Scenario files\\
+\hline
+IdType.Map & Scenario map\\
+\hline
+IdType.MapBlock & Blocks of scenario map\\
+\hline
+IdType.ScenarioInfo & Scenario information\\
+\hline
+IdType.SpellEffects &\\
+\hline
+IdType.Fortification & Capitals and villages\\
+\hline
+IdType.Player & Players in scenario\\
+\hline
+IdType.PlayerKnownSpells & Spells known by player in scenario\\
+\hline
+IdType.Fog & Fog of war for player in scenario\\
+\hline
+IdType.PlayerBuildings & Capital buildings\\
+\hline
+IdType.Road & Roads on scenario map\\
+\hline
+IdType.Stack & Stacks in scenario\\
+\hline
+IdType.Unit & Units in scenario\\
+\hline
+IdType.Landmark & Landmarks in scenario\\
+\hline
+IdType.Item & Items in scenario\\
+\hline
+IdType.Bag & Bags in scenario\\
+\hline
+IdType.Site & Sites in scenario\\
+\hline
+IdType.Ruin & Ruins in scenario\\
+\hline
+IdType.Tomb & Grave markers in scenario\\
+\hline
+IdType.Rod & Rods in scenario\\
+\hline
+IdType.Crystal & Gold mines and mana sources in scenario\\
+\hline
+IdType.Diplomacy & Diplomacy rules in scenario\\
+\hline
+IdType.SpellCast &\\
+\hline
+\end{tabularx}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+IdType.Location & Location on scenario map\\
+\hline
+IdType.StackTemplate & Stack templates in scenario\\
+\hline
+IdType.Event & Events in scenario\\
+\hline
+IdType.StackDestroyed & Information about stacks defeated in scenario\\
+\hline
+IdType.TalismanCharges & Talisman charges counter in scenario\\
+\hline
+IdType.Mountains & Mountains in scenario\\
+\hline
+IdType.SubRace & Subraces in scenario\\
+\hline
+IdType.SubRaceType & Entries of \texttt{GSubRace.dbf}\\
+\hline
+IdType.QuestLog & Scenario quest log\\
+\hline
+IdType.TurnSummary & Brief information about last turn in scenario\\
+\hline
+IdType.ScenarioVariable & Scenario variables\\
+\hline
+\end{tabularx}
+
+\subsubsection{Difficulty}
+\label{Difficulty}
+Difficulty types correspond to the contents of \texttt{Ldiff.dbf} and can be accessed using table \texttt{Difficulty}.
+By default, table contains following difficulty types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Easy &\\
+\hline
+Average &\\
+\hline
+Hard &\\
+\hline
+VeryHard &\\
+\hline
+\end{tabularx}
+
+\subsubsection{Building}
+\label{BuildingCategory}
+Building types correspond to the contents of \texttt{Lbuild.dbf} and can be accessed using table \texttt{Building}.
+By default, table contains following building types:\\
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Guild & Allows to hire thieves\\
+\hline
+Heal & Temple, necessary for healing and resurrection in towns\\
+\hline
+Magic & Mage tower, necessary for spells research\\
+\hline
+Unit & Building that is needed to upgrade units\\
+\hline
+\end{tabularx}
+
+\subsubsection{UnitBranch}
+\label{UnitBranch}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Fighter &\\
+\hline
+Archer &\\
+\hline
+Mage &\\
+\hline
+Special &\\
+\hline
+Sideshow &\\
+\hline
+Hero &\\
+\hline
+Noble &\\
+\hline
+Summon &\\
+\hline
+\end{tabularx}
\ No newline at end of file
diff --git a/ApiDocs/en/eventConditionsExample.tex b/ApiDocs/en/eventConditionsExample.tex
new file mode 100644
index 00000000..e0e5afb8
--- /dev/null
+++ b/ApiDocs/en/eventConditionsExample.tex
@@ -0,0 +1,43 @@
+\subsection{Event conditions}
+\subsubsection{Check if all tiles in location have the same terrain (Human)}
+\begin{center}
+\begin{lstlisting}[language=Lua]
+-- You can use lambda functions freely
+local forEachTile = function (location, f)
+ local pos = location.position
+ -- Use integers for tile coordinates
+ local halfR = math.floor(location.radius / 2)
+ local startX = pos.x - halfR
+ local startY = pos.y - halfR
+ local endX = pos.x + halfR
+ local endY = pos.y + halfR
+
+ for x = startX,endX,1 do
+ for y = startY,endY,1 do
+ f(x, y)
+ end
+ end
+end
+
+local location = scenario:getLocation('S143LO0000')
+if not location then
+ return false
+end
+
+local tilesTotal = location.radius * location.radius
+local count = 0
+
+forEachTile(location, function (x, y)
+ local tile = scenario:getTile(x, y)
+ if not tile then
+ return false
+ end
+
+ if tile.terrain == Terrain.Human then
+ count = count + 1
+ end
+end)
+
+return tilesTotal == count
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/examples.tex b/ApiDocs/en/examples.tex
new file mode 100644
index 00000000..7b46e4d8
--- /dev/null
+++ b/ApiDocs/en/examples.tex
@@ -0,0 +1,18 @@
+\section{Examples}
+See \href{https://github.com/VladimirMakeev/D2ModdingToolset/tree/master/Scripts}{Scripts} directory for additional examples.
+\input{doppelgangerExample.tex}
+\newpage
+\input{transformSelfExample.tex}
+\newpage
+\input{transformOtherExample.tex}
+\newpage
+\input{drainLevelExample.tex}
+\newpage
+\input{summonExample.tex}
+\newpage
+\input{customReachExample.tex}
+\newpage
+\input{eventConditionsExample.tex}
+\newpage
+\input{customModifiersExample.tex}
+\newpage
\ No newline at end of file
diff --git a/ApiDocs/en/fog.tex b/ApiDocs/en/fog.tex
new file mode 100644
index 00000000..1e44eeaf
--- /dev/null
+++ b/ApiDocs/en/fog.tex
@@ -0,0 +1,14 @@
+\subsection{Fog}
+\label{Fog}
+Represents \hyperref[Player]{player's} fog of war.
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+getFog(x, y) & Returns \texttt{true} if specified map position is covered by fog of war. Map position can be specified by pair of coordinates or a \hyperref[Point]{point}\\
+getFog(Point.new(3, 7)) &\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/fort.tex b/ApiDocs/en/fort.tex
new file mode 100644
index 00000000..cb86e914
--- /dev/null
+++ b/ApiDocs/en/fort.tex
@@ -0,0 +1,31 @@
+\subsection{Fort}
+\label{Fort}
+Represents Capital or City on a map. Fort contains a garrison \hyperref[Group]{group} of 6 unit \hyperref[UnitSlot]{slots}. Note that the garrison group is different to a group of visiting \hyperref[Stack]{stack}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns fort \hyperref[Id]{id}. The value is unique for every fort on scenario map\\
+\hline
+position & Returns fort position as a \hyperref[Point]{point}\\
+\hline
+entrance & Return fort entrance coordinates as a \hyperref[Point]{point}\\
+\hline
+owner & Returns \hyperref[Player]{player} that owns the fort. Neutral forts are owned by neutral player\\
+\hline
+group & Returns fort units as a \hyperref[Group]{group}\\
+\hline
+visitor & Returns visitor \hyperref[Stack]{stack}, or \texttt{nil} if none\\
+\hline
+subrace & Returns fort \hyperref[SubraceCategory]{subrace}\\
+\hline
+inventory & Returns array of inventory \hyperref[Item]{items}\\
+\hline
+capital & Returns \texttt{true} if fort is a capital city\\
+\hline
+tier & Returns fort tier (level). Tiers are in range \texttt{[1 : 6]}. Tier 6 corresponds to the capital city\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/game.tex b/ApiDocs/en/game.tex
new file mode 100644
index 00000000..08028fe2
--- /dev/null
+++ b/ApiDocs/en/game.tex
@@ -0,0 +1,21 @@
+\subsection{Game}
+\label{Game}
+Represents game restrictions and constants. Allows to access \texttt{settings.lua}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+unitMaxDamage & Maximum damage unit attack can inflict in battle. \texttt{unitMaxDamage} from \texttt{settings.lua}\\
+\hline
+unitMinDamage & Minimum damage unit attack can inflict in battle. Currently 1\\
+\hline
+unitMaxArmor & Maximum armor unit can have. \texttt{unitMaxArmor} from \texttt{settings.lua}\\
+\hline
+leaderAdditionalDamage & Additional damage granted by leader ability 'Heavy strike'. Currently 100\\
+\hline
+editor & Returns \texttt{true} if Scenario Editor (\texttt{ScenEdit.exe}) is running, \texttt{false} if game (\texttt{Discipl2.exe}) \\
+\hline
+\end{tabularx}
+\end{center}
diff --git a/ApiDocs/en/global.tex b/ApiDocs/en/global.tex
new file mode 100644
index 00000000..7c17d5da
--- /dev/null
+++ b/ApiDocs/en/global.tex
@@ -0,0 +1,13 @@
+\subsection{Global}
+\label{Global}
+Represents global data storage used by game. Allows to access contents of dbf files in \texttt{Globals} folder of the game.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+variables & Returns \hyperref[GlobalVariables]{global variables}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/globalvariables.tex b/ApiDocs/en/globalvariables.tex
new file mode 100644
index 00000000..7814b1f3
--- /dev/null
+++ b/ApiDocs/en/globalvariables.tex
@@ -0,0 +1,148 @@
+\subsection{GlobalVariables}
+\label{GlobalVariables}
+Allows to access contents of \texttt{GVars.dbf}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description}\\
+\hline
+weapnMstr & Instructor skill bonus experience, \texttt{WEAPN\_MSTR}\\
+\hline
+batInit & Max additional initiative points that randomly added to unit in battle. \texttt{BAT\_INIT}\\
+\hline
+batDamage & Max additional damage points that randomly added to unit damage in battle. \texttt{BAT\_DAMAGE}\\
+\hline
+batRound & \texttt{BAT\_ROUND}\\
+\hline
+batBreak & \texttt{BAT\_BREAK}\\
+\hline
+batBModif & \texttt{BAT\_BMODIF}\\
+\hline
+batLoweri & Initiative debuff. \texttt{BATLOWERI}\\
+\hline
+ldrMaxAbil & Maximum number of abilities leader can learn. \texttt{LDRMAXABIL}\\
+\hline
+spyDiscov & Spy discovery chance per turn. \texttt{SPY\_DISCOV}\\
+\hline
+poisonC & Damage from thief action 'poison city'. \texttt{POISON\_C}\\
+\hline
+poisonS & Damage from thief action 'poison stack'. \texttt{POISON\_S}\\
+\hline
+bribe & Bribe multiplier. \texttt{BRIBE}\\
+\hline
+stealRace & \texttt{STEAL\_RACE}\\
+\hline
+stealNeut & \texttt{STEAL\_NEUT}\\
+\hline
+riotMin & Minimal riot duration in days. \texttt{RIOT\_MIN}\\
+\hline
+riotMax & Maximal riot duration in days. \texttt{RIOT\_MAX}\\
+\hline
+riotDmg & Percentage of riot damage. \texttt{RIOT\_DMG}\\
+\hline
+sellRatio & Percentage of the original price of the items at sale. \texttt{SELL\_RATIO}\\
+\hline
+tCapture & Land transformation after city capture. \texttt{T\_CAPTURE}\\
+\hline
+tCapital & Land transformation per turn by capital. \texttt{T\_CAPITAL}\\
+\hline
+rodRange & Range of land transformation by rod per turn. \texttt{ROD\_RANGE}\\
+\hline
+crystalP & Profit per mana crystal or gold mine per turn. \texttt{CRYSTAL\_P}\\
+\hline
+constUrg & \texttt{CONST\_URG}\\
+\hline
+regenLwar & Bonus per day regeneration for fighter leader. \texttt{REGEN\_LWAR}\\
+\hline
+regenRuin & Bonus per day regeneration for units in ruins. \texttt{REGEN\_RUIN}\\
+\hline
+dPeace & Diplomacy level representing peace. \texttt{D\_PEACE}\\
+\hline
+dWar & Diplomacy level representing war. \texttt{D\_WAR}\\
+\hline
+dNeutral & Diplomacy level representing neutrality. \texttt{D\_NEUTRAL}\\
+\hline
+dGold & \texttt{D\_GOLD}\\
+\hline
+dMkAlly & \texttt{D\_MK\_ALLY}\\
+\hline
+dAttakSc & \texttt{D\_ATTACK\_SC}\\
+\hline
+dAttakFo & \texttt{D\_ATTACK\_FO}\\
+\hline
+dAttakPc & \texttt{D\_ATTACK\_PC}\\
+\hline
+dRod & \texttt{D\_ROD}\\
+\hline
+dRefAlly & \texttt{D\_REF\_ALLY}\\
+\hline
+dBkAlly & \texttt{D\_BK\_ALLY}\\
+\hline
+dNoble & \texttt{D\_NOBLE}\\
+\hline
+dBkaChnc & \texttt{D\_BKA\_CHANCE}\\
+\hline
+dBkaTurn & \texttt{D\_BKA\_TURN}\\
+\hline
+\end{tabularx}
+\end{center}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description}\\
+\hline
+protCap & Capital protection. \texttt{PROT\_CAP}\\
+\hline
+bonusE & Additional gold on easy difficulty. \texttt{BONUS\_E}\\
+\hline
+bonusA & Additional gold on average difficulty. \texttt{BONUS\_A}\\
+\hline
+bonusH & Additional gold on hard difficulty. \texttt{BONUS\_H}\\
+\hline
+bonusV & Additional gold on very hard difficulty. \texttt{BONUS\_V}\\
+\hline
+incomeE & Income increase on easy difficulty. \texttt{INCOME\_E}\\
+\hline
+incomeA & Income increase on average difficulty. \texttt{INCOME\_A}\\
+\hline
+incomeH & Income increase on hard difficulty. \texttt{INCOME\_H}\\
+\hline
+incomeV & Income increase on very hard difficulty. \texttt{INCOME\_V}\\
+\hline
+guRange & \texttt{GU\_RANGE}\\
+\hline
+paRange & \texttt{PA\_RANGE}\\
+\hline
+loRange & \texttt{LO\_RANGE}\\
+\hline
+defendBonus & Armor bonus when unit uses defend in battle. \texttt{DFENDBONUS}\\
+\hline
+talisChrg & \texttt{TALIS\_CHRG}\\
+\hline
+gainSpell & Chance to get spells with capture of a capital. \texttt{GAIN\_SPELL}\\
+\hline
+rodCost & Rod placement cost. \texttt{ROD\_COST}\\
+\hline
+\end{tabularx}
+\end{center}
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description}\\
+\hline
+morale(n) & Input tier values must be in range \texttt{[1 : 6]}. \texttt{MORALE\_n}\\
+\hline
+batBoostd(n) & Damage boost values for various levels. Levels must be in range \texttt{[1 : 4]}. \texttt{BATBOOSTDn}\\
+\hline
+batLowerd(n) & Damage debuff values for various levels. Levels must be in range \texttt{[1 : 2]}. \texttt{BATLOWERDn}\\
+\hline
+tCity(n) & Land transformation per turn by cities of different tiers. Tier must be in range \texttt{[1 : 5]}. \texttt{T\_CITYn}\\
+\hline
+prot(n) & City protection values for various tier levels. Tier must be in range \texttt{[1 : 6]}. In case of tier 6, returns \texttt{protCap}. \texttt{PROT\_n}\\
+\hline
+splPwr(n) & Input tier values must be in range \texttt{[1 : 5]}. \texttt{SPLPWR\_n}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/group.tex b/ApiDocs/en/group.tex
new file mode 100644
index 00000000..0fe361a0
--- /dev/null
+++ b/ApiDocs/en/group.tex
@@ -0,0 +1,35 @@
+\subsection{Group}
+\label{Group}
+Represents 6 unit \hyperref[UnitSlot]{slots}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns group \hyperref[Id]{id}\\
+\hline
+slots & Returns group as an array of 6 \hyperref[UnitSlot]{slots}\\
+\hline
+units & Returns group as an array of \hyperref[Unit]{units}\\
+\hline
+subrace & Returns group \hyperref[SubraceCategory]{subrace}. In case of group inside \hyperref[Ruin]{ruin}, returns -1 since ruins do not belong to subraces\\
+\hline
+\end{tabularx}
+\end{center}
+
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+hasUnit(unit) & Returns true if group has specified \hyperref[Unit]{unit}\\
+\hline
+hasUnit(Id.new('S143UN0001')) & Returns true if group has specified unit \hyperref[Id]{id}\\
+\hline
+getUnitPosition(unit) & Returns \hyperref[Unit]{unit} position in group, or -1 if unit not found\\
+getUnitPosition(unit.id) &\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/id.tex b/ApiDocs/en/id.tex
new file mode 100644
index 00000000..362d5b9c
--- /dev/null
+++ b/ApiDocs/en/id.tex
@@ -0,0 +1,38 @@
+\subsection{Id}
+\label{Id}
+Represents object identifier. Identifiers used to search scenario objects.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+value & Returns integer representation of id. Can be used as Lua table key for best performance.\\
+\hline
+typeIndex & Returns identified object index among the same type of scenario objects (units, stacks, items, etc.). Can be used as Lua table key for best performance.\\
+\hline
+type & Returns \hyperref[IdType]{type} of identifier. Identifier type can help to distinguish one object from another\\
+\hline
+\end{tabularx}
+\end{center}
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Id.new('S143KC0001') & Creates id from string\\
+\hline
+Id.emptyId() & Returns empty identifier\\
+\hline
+tostring(id) & Converts id to string\\
+\hline
+Id.summonId(position) & Creates special id for summoning units in battle using specified position in group. Position in group should be in \texttt{[0 : 5]} range.\\
+\hline
+\end{tabularx}
+\end{center}
+%\paragraph{Examples:}
+%\begin{center}
+%\begin{lstlisting}[language=Lua]
+%\end{lstlisting}
+%\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/item.tex b/ApiDocs/en/item.tex
new file mode 100644
index 00000000..d3e95df8
--- /dev/null
+++ b/ApiDocs/en/item.tex
@@ -0,0 +1,17 @@
+\subsection{Item}
+\label{Item}
+Represents item object in the current scenario.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns item \hyperref[Id]{id}. This is different to id of \hyperref[ItemBase]{item base}. The value is unique for every item on scenario map\\
+\hline
+base & Returns \hyperref[ItemBase]{item base}\\
+\hline
+sellValue & Returns item \hyperref[Currency]{sell value}, it accounts global sell ratio and used talisman charges (if applicable)\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/itembase.tex b/ApiDocs/en/itembase.tex
new file mode 100644
index 00000000..9bde4ef7
--- /dev/null
+++ b/ApiDocs/en/itembase.tex
@@ -0,0 +1,21 @@
+\subsection{Item base}
+\label{ItemBase}
+Represents base item of any type (described in \texttt{GItem.dbf})
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns item \hyperref[Id]{id}. \texttt{ITEM\_ID} value from \texttt{GItem.dbf}\\
+\hline
+type & Returns item \hyperref[ItemCategory]{type}\\
+\hline
+value & Returns item \hyperref[Currency]{value}\\
+\hline
+unitImpl & Returns related \hyperref[UnitImpl]{unit implementation}. For instance: in case of `Angel Orb', Angel unit implementation is returned\\
+\hline
+attack & Returns \hyperref[Attack]{attack} that this item performs (in case of orb or talisman), or \texttt{nil} if no attack is associated with the item. For instance: in case of `Orb of Fire', corresponding attack from \texttt{Gattacks.dbf} is returned\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/location.tex b/ApiDocs/en/location.tex
new file mode 100644
index 00000000..a670eb22
--- /dev/null
+++ b/ApiDocs/en/location.tex
@@ -0,0 +1,19 @@
+\subsection{Location}
+\label{Location}
+Represents location object in scenario.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns location \hyperref[Id]{id}. The value is unique for every location on scenario map\\
+\hline
+position & Returns location position as a \hyperref[Point]{point}\\
+\hline
+radius & Returns radius of location\\
+\hline
+name & Returns location name\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/mercenary.tex b/ApiDocs/en/mercenary.tex
new file mode 100644
index 00000000..1a602d37
--- /dev/null
+++ b/ApiDocs/en/mercenary.tex
@@ -0,0 +1,19 @@
+\subsection{Mercenary camp}
+\label{Mercenary}
+Represents Mercenary camp on a map.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns mercenary camp \hyperref[Id]{id}. The value is unique for every mercenary camp on scenario map\\
+\hline
+position & Returns mercenary camp position as a \hyperref[Point]{point}\\
+\hline
+visitors & Returns list of \hyperref[Player]{players} that have visited the mercenary camp\\
+\hline
+units & Returns list of \hyperref[MercenaryUnit]{mercenary units}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/mercenaryunit.tex b/ApiDocs/en/mercenaryunit.tex
new file mode 100644
index 00000000..55348f2b
--- /dev/null
+++ b/ApiDocs/en/mercenaryunit.tex
@@ -0,0 +1,15 @@
+\subsection{Mercenary unit}
+\label{MercenaryUnit}
+Represents unit for hire in \hyperref[Mercenary]{mercenary camp}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+impl & Returns \hyperref[UnitImpl]{unit implementation}\\
+\hline
+unique & Returns \texttt{true} is unit can be hired only once\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/merchant.tex b/ApiDocs/en/merchant.tex
new file mode 100644
index 00000000..bc0eb074
--- /dev/null
+++ b/ApiDocs/en/merchant.tex
@@ -0,0 +1,21 @@
+\subsection{Merchant}
+\label{Merchant}
+Represents Merchant on a map.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns merchant \hyperref[Id]{id}. The value is unique for every merchant on scenario map\\
+\hline
+position & Returns merchant position as a \hyperref[Point]{point}\\
+\hline
+visitors & Returns list of \hyperref[Player]{players} that have visited the merchant\\
+\hline
+items & Returns list of \hyperref[MerchantItem]{merchant items}\\
+\hline
+temple & Returns \texttt{true} if merchant can be used as a temple for AI to heal\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/merchantitem.tex b/ApiDocs/en/merchantitem.tex
new file mode 100644
index 00000000..285b15a0
--- /dev/null
+++ b/ApiDocs/en/merchantitem.tex
@@ -0,0 +1,15 @@
+\subsection{Merchant item}
+\label{MerchantItem}
+Represents item sold by \hyperref[Merchant]{merchant}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+base & Returns \hyperref[ItemBase]{base item}\\
+\hline
+amount & Returns amount of items in merchant stock\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/modifier.tex b/ApiDocs/en/modifier.tex
new file mode 100644
index 00000000..fd531a1b
--- /dev/null
+++ b/ApiDocs/en/modifier.tex
@@ -0,0 +1,13 @@
+\subsection{Modifier}
+\label{Modifier}
+Represents unit modifier. Modifiers wrap \hyperref[UnitImpl]{unit implementation}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns modifier \hyperref[Id]{id}. \texttt{MODIF\_ID} value from \texttt{Gmodif.dbf}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/overview.tex b/ApiDocs/en/overview.tex
new file mode 100644
index 00000000..6001377c
--- /dev/null
+++ b/ApiDocs/en/overview.tex
@@ -0,0 +1,25 @@
+\section{Overview}
+All scripts are expected to be written using \href{https://www.lua.org/}{Lua} language \href{https://www.lua.org/ftp/lua-5.4.1.tar.gz}{version 5.4.1} and should be placed in Scripts folder. Scripts folder itself should be placed in the game folder.
+
+
+Currently used scripts and their meanings:
+\begin{itemize}
+\item settings.lua - mss32 proxy dll settings that changes game rules
+\item doppelganger.lua - logic that computes level of doppelganger transform (category \texttt{L\_DOPPELGANGER})
+\item transformSelf.lua - computes unit level and determines free attacks to give for transform-self attacks (category \texttt{L\_TRANSFORM\_SELF})
+\item transformOther.lua - computes unit level for transform-other attacks (category \texttt{L\_TRANSFORM\_OTHER})
+\item summon.lua - computes summoned unit level for summon attacks (category \texttt{L\_SUMMON})
+\item textids.lua - contains interface text mapping for custom functionality
+\item getAllTargets.lua - contains selection/attack targeting logic for \texttt{L\_ANY}/\texttt{L\_ALL} attack reaches
+\item getAdjacentTargets.lua - contains selection/attack targeting logic for adjacent/all-adjacent attack reach
+\item getSelectedTargetAndAllAdjacentToIt.lua - contains attack targeting logic for selective-cleave attack reach
+\item getSelectedTargetAndOneAdjacentToIt.lua - contains attack targeting logic for single selective-cleave attack reach
+\item getSelectedTargetAndOneBehindIt.lua - contains attack targeting logic for pierce attack reach
+\item getSelectedLineTargets.lua - contains attack targeting logic for wide-cleave attack reach
+\item getSelectedColumnTargets.lua - contains attack targeting logic for column attack reach
+\item getSelectedArea2x2Targets.lua - contains attack targeting logic for 2x2 area splash attack reach
+\item getSelectedTargetAndTwoChainedRandom.lua - contains attack targeting logic for random chain attack reach
+\item getSelectedTargetAndOneRandom.lua - contains attack targeting logic for additional random target
+\item getWoundedFemaleGreenskinTargets.lua - contains targeting logic that only allows to reach wounded female greenskins
+\item \href{https://github.com/VladimirMakeev/D2ModdingToolset/tree/master/Scripts/Modifiers}{Scripts/Modifiers} - contain custom modifier script examples
+\end{itemize}
diff --git a/ApiDocs/en/player.tex b/ApiDocs/en/player.tex
new file mode 100644
index 00000000..bf69b5b8
--- /dev/null
+++ b/ApiDocs/en/player.tex
@@ -0,0 +1,39 @@
+\subsection{Player}
+\label{Player}
+Represents game player including AI and neutrals.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns player \hyperref[Id]{id}. The value is unique for every player on scenario map\\
+\hline
+race & Returns player \hyperref[RaceCategory]{race}\\
+\hline
+lord & Returns player \hyperref[LordCategory]{lord type}\\
+\hline
+bank & Returns player \hyperref[Currency]{bank}\\
+\hline
+human & Returns \texttt{true} if player is human (not AI)\\
+\hline
+alwaysAi & Returns \texttt{true} if player is always AI\\
+\hline
+fog & Returns player's \hyperref[Fog]{fog of war}. In fully loaded scenario, player objects always have fog of war. During scenario loading this property can return \texttt{nil}\\
+\hline
+buildings & Returns list of \hyperref[Building]{buildings} that are already built\\
+\hline
+\end{tabularx}
+\end{center}
+
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+hasBuilding('g000bb0001') & Returns \texttt{true} if player has built building with specified id\\
+hasBuilding(Id.new('g000bb0001')) & Method also accepts \hyperref[Id]{ids}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/point.tex b/ApiDocs/en/point.tex
new file mode 100644
index 00000000..fd3ac95e
--- /dev/null
+++ b/ApiDocs/en/point.tex
@@ -0,0 +1,40 @@
+\subsection{Point}
+\label{Point}
+Represents point in 2D space.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+x & Access x coordinate for reading and writing\\
+\hline
+y & Access y coordinate for reading and writing\\
+\hline
+\end{tabularx}
+\end{center}
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+Point.new() & Creates point with both coordinates set to 0\\
+\hline
+Point.new(x, y) & Creates point with specified coordinates\\
+\hline
+tostring(p) & Converts point to string \texttt{'(x, y)'}\\
+\hline
+\end{tabularx}
+\end{center}
+\subsubsection{Examples}
+\begin{center}
+\begin{lstlisting}[language=Lua]
+-- x = 0, y = 0
+local p = Point.new()
+-- x = 1, y = 3
+local p2 = Point.new(1, 3)
+-- s == '(1, 3)'
+local s = tostring(p2)
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/resourcemarket.tex b/ApiDocs/en/resourcemarket.tex
new file mode 100644
index 00000000..198619ef
--- /dev/null
+++ b/ApiDocs/en/resourcemarket.tex
@@ -0,0 +1,180 @@
+\subsection{Resource market}
+\label{ResourceMarket}
+Represents resource market on a map.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns market \hyperref[Id]{id}. The value is unique for every resource market on scenario map\\
+\hline
+position & Returns market position as a \hyperref[Point]{point}\\
+\hline
+visitors & Returns list of \hyperref[Player]{players} that have visited the market\\
+\hline
+stock & Returns market \hyperref[Currency]{stock}\\
+\hline
+customRates & Returns \texttt{true} if resource market uses custom exchange rates\\
+\hline
+\end{tabularx}
+\end{center}
+
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+isInfinite(Resource.Gold) & Returns \texttt{true} if market has infinite amount of specified \hyperref[Resource]{resource}\\
+\hline
+\end{tabularx}
+\end{center}
+
+\subsubsection{Custom exchange rates}
+Custom exchange rates are defined by Lua script which must declare function with a predefined name:
+\texttt{getExchangeRates}.
+Function must return table with exchanges that are possible. Empty table means that market could not exchange resources at all.\\
+Function signature:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getExchangeRates(visitorStack, market, serverSide)
+ return { }
+end
+\end{lstlisting}
+\end{center}
+Arguments are:
+\begin{itemize}
+\item \texttt{visitorStack} - \hyperref[Stack]{stack} that is currently visiting the market
+\item \texttt{market} - \hyperref[ResourceMarket]{resource market} itself
+\item \texttt{serverSide} - \texttt{true} if exchange rates script is executed by the server
+\end{itemize}
+Possible exchange is represented by a table that specifies resource that player can sell and a list of resources it can get, with rates.\\
+Format is shown by the following listing:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+{
+ player_resource,
+ { market_resource, player_resource_amount, market_resource_amount }
+}
+\end{lstlisting}
+\end{center}
+Exchange that allows player to sell 2 gold for 1 life mana:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+{
+ Resource.Gold,
+ { Resource.LifeMana, 2, 1 }
+}
+\end{lstlisting}
+\end{center}
+Exchange that allows player to sell gold: 2 gold for 1 life mana or 7 gold for 4 death mana:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+{
+ Resource.Gold,
+ { Resource.LifeMana, 2, 1 },
+ { Resource.DeathMana, 7, 4 }
+}
+\end{lstlisting}
+\end{center}
+Exchange that allows player to sell runic mana: 1 runic mana for 10 gold or 4 runic mana for 2 grove mana:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+{
+ Resource.RunicMana,
+ { Resource.Gold, 1, 10 },
+ { Resource.GroveMana, 4, 2 }
+}
+\end{lstlisting}
+\end{center}
+Market exchange rates table must contain list of possible exchanges. Here is the example of exchange rates when player is allowed to trade gold for life mana and life mana for other mana types:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+{
+ {
+ Resource.Gold,
+ { Resource.LifeMana, 2, 1 }
+ },
+ {
+ Resource.LifeMana,
+ { Resource.DeathMana, 4, 1 },
+ { Resource.RunicMana, 4, 1 },
+ { Resource.InfernalMana, 4, 1 },
+ { Resource.GroveMana, 4, 1 }
+ }
+}
+\end{lstlisting}
+\end{center}
+\texttt{getExchangeRates} function must return such table.\\
+\subsubsection{Examples}
+Custom exchange rates where market is only allowed to exchange 7 gold for 1 life mana:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getExchangeRates(visitorStack, market, serverSide)
+ return {
+ {
+ Resource.Gold,
+ { Resource.LifeMana, 7, 1 }
+ }
+ }
+end
+\end{lstlisting}
+\end{center}
+\newpage
+All functions and objects of Lua API can be used in custom exchange rates scripts. This allows mod and map makers to check for lord types, specific leaders or stack groups, units and more.\\
+Example of custom exchange rates where exchange rates depend on number of previously visited resource markets:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+-- Returns true if market was visited by specified race
+local function isMarketVisited(market, race)
+ local visitors = market.visitors
+ for i = 1, #visitors do
+ local visitor = visitors[i]
+ if visitor.race == race then
+ return true
+ end
+ end
+
+ return false
+end
+
+function getExchangeRates(visitor, currentMarket, serverSide)
+ local visitorRace = visitor.owner.race
+ local marketsVisited = 0
+
+ local countVisitedMarkets = function (market)
+ if isMarketVisited(market, visitorRace) then
+ marketsVisited = marketsVisited + 1
+ end
+ end
+
+ -- Count how many markets we have visited so far
+ getScenario():forEachMarket(countVisitedMarkets)
+
+ -- Check if current resource market is already visited.
+ -- If not, it means we entered it for the first time
+ -- and game have not yet marked it as visited.
+ if not isMarketVisited(currentMarket, visitorRace) then
+ marketsVisited = marketsVisited + 1
+ end
+
+ local exchangeRate = { 100, 50, 25, 12 }
+ -- Make sure index stays within array bounds
+ marketsVisited = math.min(marketsVisited, #exchangeRate)
+
+ local rate = exchangeRate[marketsVisited]
+
+ return {
+ {
+ Resource.Gold,
+ {
+ -- Exchange (100 / 50 / 25 / 12) gold for 50 life mana
+ -- Rate depends on how many resource markets player have visited so far
+ { Resource.LifeMana, rate, 50 }
+ }
+ }
+ }
+end
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/rod.tex b/ApiDocs/en/rod.tex
new file mode 100644
index 00000000..a878a98f
--- /dev/null
+++ b/ApiDocs/en/rod.tex
@@ -0,0 +1,17 @@
+\subsection{Rod}
+\label{Rod}
+Represents rod object in scenario. Rods are planted to transform terrain and capture resources.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns rod \hyperref[Id]{id}. The value is unique for every rod on scenario map\\
+\hline
+position & Returns rod position as a \hyperref[Point]{point}\\
+\hline
+owner & Returns \hyperref[Player]{player} that planted the rod\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/ruin.tex b/ApiDocs/en/ruin.tex
new file mode 100644
index 00000000..7b7d4cf9
--- /dev/null
+++ b/ApiDocs/en/ruin.tex
@@ -0,0 +1,23 @@
+\subsection{Ruin}
+\label{Ruin}
+Represents Ruin on a map. Ruin contains a garrison \hyperref[Group]{group} of 6 unit \hyperref[UnitSlot]{slots}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns ruin \hyperref[Id]{id}. The value is unique for every ruin on scenario map\\
+\hline
+position & Returns ruin position as a \hyperref[Point]{point}\\
+\hline
+looter & Returns \hyperref[Player]{player} that have looted the ruin or \texttt{nil} if none\\
+\hline
+group & Returns ruin units as a \hyperref[Group]{group}\\
+\hline
+item & Returns \hyperref[Item]{item} reward for looting the ruin or \texttt{nil} if none\\
+\hline
+cash & Returns \hyperref[Currency]{cash} reward for looting the ruin\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/scenario.tex b/ApiDocs/en/scenario.tex
new file mode 100644
index 00000000..6df7192c
--- /dev/null
+++ b/ApiDocs/en/scenario.tex
@@ -0,0 +1,189 @@
+\subsection{Scenario}
+\label{Scenario}
+Represents scenario map with all its objects and state
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+name & Returns scenario name or empty string if scenario is unnamed\\
+\hline
+description & Returns scenario description or empty string if scenario has no description\\
+\hline
+author & Returns scenario author or empty string if no author specified\\
+\hline
+seed & Returns scenario initial seed used by random generator\\
+\hline
+day & Returns number of current day in game\\
+\hline
+size & Returns scenario map size\\
+\hline
+difficulty & Returns scenario map \hyperref[Difficulty]{difficulty} selected by player\\
+\hline
+variables & Returns \hyperref[ScenarioVariables]{scenario variables}. If scenario has no variables defined, returns \texttt{nil}\\
+\hline
+diplomacy & Returns object that holds \hyperref[Diplomacy]{diplomacy} relations between races. Fully loaded scenario always have diplomacy relations. During scenario loading this property can return \texttt{nil}\\
+\hline
+\end{tabularx}
+\end{center}
+
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+getLocation('S143LO0001') & Searches for \hyperref[Location]{location} by id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getLocation(Id.new('S143LO0001')) & \\
+\hline
+getPlayer('S143PL0001') & Searches for \hyperref[Player]{player} by id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getPlayer(Id.new('S143PL0001')) & \\
+\hline
+getUnit('S143UN0007') & Searches for \hyperref[Unit]{unit} by id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getUnit(Id.new('S143UN0007')) & \\
+\hline
+getItem('S143IM0001') & Searches for \hyperref[Item]{item} by id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getItem(Id.new('S143IM0001')) & \\
+\hline
+getTile(3, 5) & Searches for \hyperref[Tile]{tile} by pair of coordinates or \hyperref[Point]{point}, returns \texttt{nil} if not found\\
+getTile(Point.new(3, 5)) &\\
+\hline
+getStack(10, 15) & Searches for \hyperref[Stack]{stack} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getStack(Point.new(10, 15)) &\\
+getStack('S143KC0005') &\\
+getStack(Id.new('S143KC0005')) &\\
+\hline
+getFort(10, 15) & Searches for \hyperref[Fort]{fort} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getFort(Point.new(10, 15)) &\\
+getFort('S143FT0005') &\\
+getFort(Id.new('S143FT0005')) &\\
+\hline
+getRuin(10, 15) & Searches for \hyperref[Ruin]{ruin} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getRuin(Point.new(10, 15)) &\\
+getRuin('S143RU0000') &\\
+getRuin(Id.new('S143RU0000')) &\\
+\hline
+getRod(10, 15) & Searches for \hyperref[Rod]{rod} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getRod(Point.new(10, 15)) &\\
+getRod('S143RD0003') &\\
+getRod(Id.new('S143RD0003')) &\\
+\hline
+getCrystal(10, 15) & Searches for \hyperref[Crystal]{crystal} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getCrystal(Point.new(10, 15)) &\\
+getCrystal('S143CR0004') &\\
+getCrystal(Id.new('S143CR0004')) &\\
+\hline
+getMerchant(10, 15) & Searches for \hyperref[Merchant]{merchant} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getMerchant(Point.new(10, 15)) &\\
+getMerchant('S143SI0002') &\\
+getMerchant(Id.new('S143SI0002')) &\\
+\hline
+getMercenary(10, 15) & Searches for \hyperref[Mercenary]{mercenary camp} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getMercenary(Point.new(10, 15)) &\\
+getMercenary('S143SI0002') &\\
+getMercenary(Id.new('S143SI0002')) &\\
+\hline
+getTrainer(10, 15) & Searches for \hyperref[Trainer]{trainer} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getTrainer(Point.new(10, 15)) &\\
+getTrainer('S143SI0002') &\\
+getTrainer(Id.new('S143SI0002')) &\\
+\hline
+getMarket(10, 15) & Searches for \hyperref[ResourceMarket]{market} by pair of coordinates, \hyperref[Point]{point}, id string or \hyperref[Id]{id}, returns \texttt{nil} if not found\\
+getMarket(Point.new(10, 15)) &\\
+getMarket('S143SI0002') &\\
+getMarket(Id.new('S143SI0002')) &\\
+\hline
+\end{tabularx}
+\end{center}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+findStackByUnit(unit) & Searches for \hyperref[Stack]{stack} that has specified \hyperref[Unit]{unit} among all the stacks in the whole scenario.\\
+findStackByUnit('S143UN0007') & You can also use unit id string or \hyperref[Id]{id}. Returns \texttt{nil} if not found.\\
+findStackByUnit(Id.new('S143UN0007')) & Note that \textbf{this search is heavy in terms of performance}, so you probably want to minimize excessive calls and use variables to store its results.\\
+\hline
+findFortByUnit(unit) & Searches for \hyperref[Fort]{fort} that has specified \hyperref[Unit]{unit} in its garrison among all the forts in the whole scenario. Only garrison units are counted, visiting stack is ignored.\\
+findFortByUnit('S143UN0007') & You can also use unit id string or \hyperref[Id]{id}. Returns \texttt{nil} if not found.\\
+findFortByUnit(Id.new('S143UN0007')) & Note that \textbf{this search is heavy in terms of performance}, so you probably want to minimize excessive calls and use variables to store its results.\\
+\hline
+findRuinByUnit(unit) & Searches for \hyperref[Ruin]{ruin} that has specified \hyperref[Unit]{unit} among all the forts in the whole scenario.\\
+findRuinByUnit('S143UN0007') & You can also use unit id string or \hyperref[Id]{id}. Returns \texttt{nil} if not found.\\
+findRuinByUnit(Id.new('S143UN0007')) & Note that \textbf{this search is heavy in terms of performance}, so you probably want to minimize excessive calls and use variables to store its results.\\
+\hline
+forEachStack(f) & Searches for every \hyperref[Stack]{stack} on a map and calls specified function on it\\
+\hline
+forEachLocation(f) & Searches for every \hyperref[Location]{location} on a map and calls specified function on it\\
+\hline
+forEachFort(f) & Searches for every \hyperref[Fort]{fort} on a map and calls specified function on it\\
+\hline
+forEachRuin(f) & Searches for every \hyperref[Ruin]{ruin} on a map and calls specified function on it\\
+\hline
+forEachRod(f) & Searches for every \hyperref[Rod]{rod} on a map and calls specified function on it\\
+\hline
+forEachPlayer(f) & Searches for every \hyperref[Player]{player} on a map and calls specified function on it\\
+\hline
+forEachUnit(f) & Searches for every \hyperref[Unit]{unit} on a map and calls specified function on it\\
+\hline
+forEachCrystal(f) & Searches for every \hyperref[Crystal]{crystal} on a map and calls specified function on it\\
+\hline
+forEachMerchant(f) & Searches for every \hyperref[Merchant]{merchant} on a map and calls specified function on it\\
+\hline
+forEachMercenary(f) & Searches for every \hyperref[Mercenary]{mercenary camp} on a map and calls specified function on it\\
+\hline
+forEachTrainer(f) & Searches for every \hyperref[Trainer]{trainer} on a map and calls specified function on it\\
+\hline
+forEachMarket(f) & Searches for every \hyperref[ResourceMarket]{market} on a map and calls specified function on it\\
+\hline
+\end{tabularx}
+\end{center}
+\newpage
+\subsubsection{Examples}
+Search for \hyperref[Location]{location} by \hyperref[Id]{id}, check if found:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+local location = scenario:getLocation('S143LO0001')
+if not location then
+ return
+end
+\end{lstlisting}
+\end{center}
+Access \hyperref[ScenarioVariables]{scenario variables}. Check for \texttt{nil} in case when no variables defined:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+local variables = scenario.variables
+if not variables then
+ return
+end
+
+local v = variables:getVariable('VAR1')
+\end{lstlisting}
+\end{center}
+Count neutral \hyperref[Stack]{stacks} on map. \texttt{forEachStack} visits each stack on a map and calls provided function which takes visited \hyperref[Stack]{stack} as its argument:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+local count = 0
+
+scenario:forEachStack(function (stack)
+ if stack.owner.race == Race.Neutral then
+ count = count + 1
+ end
+end)
+\end{lstlisting}
+\end{center}
+The same logic can be implemented by storing function that count stacks in a variable:
+\begin{center}
+\begin{lstlisting}[language=Lua]
+local count = 0
+
+local countNeutralStacks = function (stack)
+ if stack.owner.race == Race.Neutral then
+ count = count + 1
+ end
+end
+
+scenario:forEachStack(countNeutralStacks)
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/scenariovariable.tex b/ApiDocs/en/scenariovariable.tex
new file mode 100644
index 00000000..6cd45879
--- /dev/null
+++ b/ApiDocs/en/scenariovariable.tex
@@ -0,0 +1,15 @@
+\subsection{Scenario variable}
+\label{ScenarioVariable}
+Represents scenario variable used by events.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+name & Returns variable name\\
+\hline
+value & Returns variable value\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/scenariovariables.tex b/ApiDocs/en/scenariovariables.tex
new file mode 100644
index 00000000..a2c06e15
--- /dev/null
+++ b/ApiDocs/en/scenariovariables.tex
@@ -0,0 +1,25 @@
+\subsection{Scenario variables}
+\label{ScenarioVariables}
+Stores \hyperref[ScenarioVariable]{scenario variables}, allows searching them by name.
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+getVariable('name') & Searches for \hyperref[ScenarioVariable]{scenario variable} by its name, returns \texttt{nil} if not found\\
+\hline
+\end{tabularx}
+\end{center}
+\subsubsection{Examples}
+\begin{center}
+\begin{lstlisting}[language=Lua]
+local variable = variables:getVariable('VAR1')
+if variable == nil then
+ return
+end
+
+-- s == 'VAR1'
+local s = variable.name
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/stack.tex b/ApiDocs/en/stack.tex
new file mode 100644
index 00000000..b4b56813
--- /dev/null
+++ b/ApiDocs/en/stack.tex
@@ -0,0 +1,49 @@
+\subsection{Stack}
+\label{Stack}
+Represents \hyperref[Group]{group} of 6 unit \hyperref[UnitSlot]{slots} on a map. One of the units is a leader.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns stack \hyperref[Id]{id}. The value is unique for every stack on scenario map\\
+\hline
+position & Returns stack position as a \hyperref[Point]{point}\\
+\hline
+owner & Returns \hyperref[Player]{player} that owns the stack. Neutral stacks are owned by neutral player\\
+\hline
+inside & Returns \hyperref[Fort]{fort} that this stack is visiting or \texttt{nil} if none\\
+\hline
+group & Returns stack units as a \hyperref[Group]{group}\\
+\hline
+leader & Returns stack leader \hyperref[Unit]{unit}\\
+\hline
+inventory & Returns array of inventory \hyperref[Item]{items}. This includes equipped items\\
+\hline
+subrace & Returns stack \hyperref[SubraceCategory]{subrace}\\
+\hline
+order & Returns stack \hyperref[OrderCategory]{order}\\
+\hline
+orderTargetId & Returns stack's order target \hyperref[Id]{id}\\
+\hline
+aiOrder & Returns stack AI \hyperref[OrderCategory]{order}\\
+\hline
+movement & Returns stack current movement points\\
+\hline
+invisible & Returns \texttt{true} if stack is invisible\\
+\hline
+battlesWon & Returns number of battles won by the stack\\
+\hline
+\end{tabularx}
+\end{center}
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+getEquippedItem(Equipment.Boots) & Returns equipped \hyperref[Item]{item} by \hyperref[EquipmentCategory]{equipment} value\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/standaloneFuncs.tex b/ApiDocs/en/standaloneFuncs.tex
new file mode 100644
index 00000000..67e4cabb
--- /dev/null
+++ b/ApiDocs/en/standaloneFuncs.tex
@@ -0,0 +1,45 @@
+\subsection{Standalone functions}
+\subsubsection{Functions}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+log('') & Writes message to \texttt{luaDebug.log} file when \texttt{debugHooks} is set to \texttt{true} in \texttt{settings.lua}.\\
+\hline
+getScenario() & Returns current scenario. The function only accessible to scripts where scenario access is appropriate:
+\begin{itemize}
+\item summon.lua
+\item doppelganger.lua
+\item transformSelf.lua
+\item transformOther.lua
+\item drainLevel.lua
+\item custom attack reach scripts
+\item custom unit modifier script
+\end{itemize}
+\\
+\hline
+randomNumber(maxValue) & Generates random number in range \texttt{[0 : maxValue)} using ingame generator.\\
+\hline
+getGlobal() & Returns \hyperref[Global]{global data storage} used by game\\
+\hline
+getGame() & Returns \hyperref[Game]{game} restrictions and constants\\
+\hline
+\end{tabularx}
+\end{center}
+\subsubsection{Examples}
+\begin{center}
+\begin{lstlisting}[language=Lua]
+local unit = getScenario():getUnit(unitId)
+log('Unit current level:' .. unit.impl.level)
+
+local n = randomNumber(100)
+
+local data = getGlobal()
+-- Access contents of GVars.dbf
+local variables = data.variables
+
+-- Access unit maximum damage, currently set in game settings
+getGame().unitMaxDamage
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/summonExample.tex b/ApiDocs/en/summonExample.tex
new file mode 100644
index 00000000..29d22257
--- /dev/null
+++ b/ApiDocs/en/summonExample.tex
@@ -0,0 +1,21 @@
+\subsection{summon.lua}
+\texttt{summoner} has type \hyperref[Unit]{Unit}. \texttt{summonImpl} is \hyperref[UnitImpl]{Unit implementation}. \texttt{item} is \hyperref[Item]{Item} used to perform the attack. \texttt{battle} specifies an information about current \hyperref[Battle]{battle}.
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getLevel(summoner, summonImpl, item, battle)
+ -- Use base level of summon if cheap item is used to summon it
+ if item and item.base.value.gold < 500 then
+ return summonImpl.level
+ end
+
+ -- Summon unit with level twice as big as summoner level
+ -- or with level of summon implementation, whichever is bigger.
+ local impl = summoner.impl
+ local summonerLevel = impl.level
+
+ local summonLevel = summonImpl.level
+
+ return math.max(summonerLevel * 2, summonLevel)
+end
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/tile.tex b/ApiDocs/en/tile.tex
new file mode 100644
index 00000000..92c5a986
--- /dev/null
+++ b/ApiDocs/en/tile.tex
@@ -0,0 +1,15 @@
+\subsection{Tile}
+\label{Tile}
+Represents map tile.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+terrain & Returns tile \hyperref[TerrainCategory]{terrain type}\\
+\hline
+ground & Returns tile \hyperref[GroundCategory]{ground type}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/trainer.tex b/ApiDocs/en/trainer.tex
new file mode 100644
index 00000000..50c32e92
--- /dev/null
+++ b/ApiDocs/en/trainer.tex
@@ -0,0 +1,17 @@
+\subsection{Trainer}
+\label{Trainer}
+Represents Trainer on a map.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns trainer \hyperref[Id]{id}. The value is unique for every trainer on scenario map\\
+\hline
+position & Returns trainer position as a \hyperref[Point]{point}\\
+\hline
+visitors & Returns list of \hyperref[Player]{players} that have visited the trainer\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/transformOtherExample.tex b/ApiDocs/en/transformOtherExample.tex
new file mode 100644
index 00000000..348dc8b3
--- /dev/null
+++ b/ApiDocs/en/transformOtherExample.tex
@@ -0,0 +1,10 @@
+\subsection{transformOther.lua}
+\texttt{attacker} and \texttt{target} have type \hyperref[Unit]{Unit}. \texttt{transformImpl} is \hyperref[UnitImpl]{Unit implementation}. \texttt{item} is \hyperref[Item]{Item} used to perform the attack. \texttt{battle} specifies information about \hyperref[Battle]{battle} state.
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getLevel(attacker, target, transformImpl, item, battle)
+ -- transform using target level with a minimum of transform impl level
+ return math.max(target.impl.level, transformImpl.level);
+end
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/transformSelfExample.tex b/ApiDocs/en/transformSelfExample.tex
new file mode 100644
index 00000000..2f31a18e
--- /dev/null
+++ b/ApiDocs/en/transformSelfExample.tex
@@ -0,0 +1,10 @@
+\subsection{transformSelf.lua}
+\texttt{unit} has type \hyperref[Unit]{Unit}. \texttt{transformImpl} is \hyperref[UnitImpl]{Unit implementation}. \texttt{item} is \hyperref[Item]{Item} used to perform the attack. \texttt{battle} specifies an information about current \hyperref[Battle]{battle}.
+\begin{center}
+\begin{lstlisting}[language=Lua]
+function getLevel(unit, transformImpl, item, battle)
+ -- Transform into current level or level of resulting unit's template, whichever is bigger.
+ return math.max(unit.impl.level, transformImpl.level)
+end
+\end{lstlisting}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/types.tex b/ApiDocs/en/types.tex
new file mode 100644
index 00000000..fd14561e
--- /dev/null
+++ b/ApiDocs/en/types.tex
@@ -0,0 +1,78 @@
+\section{Data types}
+
+\input{point.tex}
+\newpage
+\input{id.tex}
+\newpage
+\input{currency.tex}
+\newpage
+\input{global.tex}
+\newpage
+\input{globalvariables.tex}
+\newpage
+\input{game.tex}
+\newpage
+\input{modifier.tex}
+\newpage
+\input{dynamicupgrade.tex}
+\newpage
+\input{itembase.tex}
+\newpage
+\input{item.tex}
+\newpage
+\input{building.tex}
+\newpage
+\input{attack.tex}
+\newpage
+\input{unit.tex}
+\newpage
+\input{unitdummy.tex}
+\newpage
+\input{unitimpl.tex}
+\newpage
+\input{unitslot.tex}
+\newpage
+\input{group.tex}
+\newpage
+\input{fog.tex}
+\newpage
+\input{player.tex}
+\newpage
+\input{stack.tex}
+\newpage
+\input{fort.tex}
+\newpage
+\input{merchantitem.tex}
+\newpage
+\input{merchant.tex}
+\newpage
+\input{mercenaryunit.tex}
+\newpage
+\input{mercenary.tex}
+\newpage
+\input{trainer.tex}
+\newpage
+\input{resourcemarket.tex}
+\newpage
+\input{ruin.tex}
+\newpage
+\input{rod.tex}
+\newpage
+\input{crystal.tex}
+\newpage
+\input{location.tex}
+\newpage
+\input{scenariovariable.tex}
+\newpage
+\input{scenariovariables.tex}
+\newpage
+\input{tile.tex}
+\newpage
+\input{diplomacy.tex}
+\newpage
+\input{scenario.tex}
+\newpage
+\input{battleturn.tex}
+\newpage
+\input{battle.tex}
+\newpage
\ No newline at end of file
diff --git a/ApiDocs/en/unit.tex b/ApiDocs/en/unit.tex
new file mode 100644
index 00000000..b332ddcc
--- /dev/null
+++ b/ApiDocs/en/unit.tex
@@ -0,0 +1,29 @@
+\subsection{Unit}
+\label{Unit}
+Represents game unit that participates in a battle, takes damage and performs attacks. Unit can also be a leader. Leaders are main units in stacks.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns unit \hyperref[Id]{id}. This is different to id of \hyperref[UnitImpl]{unit implementation}. The value is unique for every unit on \hyperref[Scenario]{scenario map}\\
+\hline
+hp & Returns unit's current hit points\\
+\hline
+hpMax & Returns unit's maximum hit points\\
+\hline
+xp & Returns unit's current experience points\\
+\hline
+impl & Returns unit's current \hyperref[UnitImpl]{implementation}. Current implementation describes unit stats according to its levels and possible transformations applied during battle\\
+\hline
+baseImpl & Returns unit's base \hyperref[UnitImpl]{implementation}. Base implementation is a record in \texttt{GUnits.dbf} that describes unit basic stats\\
+\hline
+leveledImpl & Returns unit's leveled (generated) \hyperref[UnitImpl]{implementation}. Leveled implementation is unit's current implementation without modifiers, or base implementation plus upgrades from \texttt{GDynUpgr.dbf} according to unit's level. This does not include leader upgrades from \texttt{GleaUpg.dbf}, because the upgrades are modifiers\\
+\hline
+original & Returns original unit \hyperref[UnitDummy]{dummy} that represents unit state before transformation, or \texttt{nil} if unit is not transformed. The state does not include any unit modifiers thus contains only leveled implementation. Unit can be transformed by transform-self, transform-other, drain-level or doppelganger attack\\
+\hline
+originalModifiers & Returns array of original \hyperref[Modifier]{modifiers} that were applied to unit before transformation, or empty array if unit is not transformed. Usually, modifiers are reapplied after transformation, but there are cases where some modifiers are incompatible with a new form, thus not getting applied to it\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/unitdummy.tex b/ApiDocs/en/unitdummy.tex
new file mode 100644
index 00000000..3594912b
--- /dev/null
+++ b/ApiDocs/en/unitdummy.tex
@@ -0,0 +1,25 @@
+\subsection{Unit dummy}
+\label{UnitDummy}
+Represents preserved state of game \hyperref[Unit]{unit}. Used, for instance, to preserve unit state before transformation, so it can be restored later. Properties are identical to unit, except that there is no \texttt{original} and \texttt{originalModifiers}.
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns unit \hyperref[Id]{id}. This is different to id of \hyperref[UnitImpl]{unit implementation}. The value is unique for every unit on \hyperref[Scenario]{scenario map}\\
+\hline
+hp & Returns unit's current hit points\\
+\hline
+hpMax & Returns unit's maximum hit points\\
+\hline
+xp & Returns unit's current experience points\\
+\hline
+impl & Returns unit's current \hyperref[UnitImpl]{implementation}. Current implementation describes unit stats according to its levels and possible transformations applied during battle\\
+\hline
+baseImpl & Returns unit's base \hyperref[UnitImpl]{implementation}. Base implementation is a record in \texttt{GUnits.dbf} that describes unit basic stats\\
+\hline
+leveledImpl & Returns unit's leveled (generated) \hyperref[UnitImpl]{implementation}. Leveled implementation is unit's current implementation without modifiers, or base implementation plus upgrades from \texttt{GDynUpgr.dbf} according to unit's level. This does not include leader upgrades from \texttt{GleaUpg.dbf}, because the upgrades are modifiers\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/unitimpl.tex b/ApiDocs/en/unitimpl.tex
new file mode 100644
index 00000000..58390621
--- /dev/null
+++ b/ApiDocs/en/unitimpl.tex
@@ -0,0 +1,115 @@
+\subsection{Unit implementation}
+\label{UnitImpl}
+Represents \hyperref[Unit]{unit} template. Records in \texttt{GUnits.dbf} are unit implementations.
+\subsubsection{How unit implementation works and why it is different to unit}
+Unit implementation is a unit template that can be used by different individual units on scenario map. It is different to unit, because different unit instances can have the same implementation. For example, you can have 3 Squires of level 1 in your party, each having the same implementation.\\
+There are 3 different stages of unit implementation that build on top of each other:
+\begin{itemize}
+\item \texttt{Global}, corresponds to a record from \texttt{GUnits.dbf};
+\begin{itemize}
+\item Returned by \hyperref[Unit]{unit}.\texttt{baseImpl} or \hyperref[UnitImpl]{impl}.\texttt{global};
+\item Its \hyperref[Id]{id} corresponds to \texttt{UNIT\_ID} from \texttt{GUnits.dbf}.
+\end{itemize}
+\item \texttt{Generated}, equals \texttt{Global} plus level upgrades from \texttt{GDynUpgr.dbf} (if any);
+\begin{itemize}
+\item Returned by \hyperref[Unit]{unit}.\texttt{leveledImpl} or \hyperref[UnitImpl]{impl}.\texttt{generated};
+\item Its \hyperref[Id]{id} is different to id of inherited \texttt{Global} implementation;
+\item If unit has no level upgrades, \hyperref[Unit]{unit}.\texttt{leveledImpl}/\hyperref[UnitImpl]{impl}.\texttt{generated} equals \hyperref[Unit]{unit}.\texttt{baseImpl}/\hyperref[UnitImpl]{impl}.\texttt{global}.
+\end{itemize}
+\item \texttt{Modified}, equals \texttt{Generated} plus applied modifiers from \texttt{Gmodif.dbf} (if any).
+\begin{itemize}
+\item Returned by \hyperref[Unit]{unit}.\texttt{impl};
+\item Its \hyperref[Id]{id} equals to id of inherited \texttt{Generated} implementation;
+\item If unit has no modifiers, \hyperref[Unit]{unit}.\texttt{impl} equals \hyperref[Unit]{unit}.\texttt{leveledImpl}/\hyperref[UnitImpl]{impl}.\texttt{generated}.
+\end{itemize}
+\end{itemize}
+\subsubsection{Unit implementation changes}
+Unit implementation changes when \hyperref[Unit]{unit}:
+\begin{itemize}
+\item Gets an upgrade, does not matter if it transforms to higher tier unit or simply gets over-level;
+\item Gets transformed: by Transform-Self, Transform-Other, Drain-Level, or Doppelganger attack;
+\item Gets \hyperref[Modifier]{modified}: when consuming a potion, affected by a spell, equipping an item, getting a leader upgrade, etc.;
+\end{itemize}
+\newpage
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+id & Returns unit implementation \hyperref[Id]{id}. \texttt{UNIT\_ID} value from \texttt{GUnits.dbf}\\
+\hline
+base & Returns base unit implementation. \texttt{BASE\_UNIT} value from \texttt{GUnits.dbf}\\
+\hline
+type & Returns unit \hyperref[UnitCategory]{type}\\
+\hline
+leaderType & Returns \hyperref[LeaderCategory]{leader type} (or -1 if unit is not a leader)\\
+\hline
+global & Returns global unit implementation - a record from \texttt{GUnits.dbf}. Same as \hyperref[Unit]{unit}.\texttt{baseImpl}\\
+\hline
+generated & Returns generated unit implementation. Equals global plus upgrades from \texttt{GDynUpgr.dbf} according to unit's level. Same as \hyperref[Unit]{unit}.\texttt{leveledImpl}\\
+\hline
+modifiers & Returns array of applied \hyperref[Modifier]{modifiers}\\
+\hline
+level & Returns unit's implementation level. \texttt{LEVEL} value from \texttt{GUnits.dbf}\\
+\hline
+xpNext & Returns experience points needed for next level. \texttt{XP\_NEXT} value from \texttt{GUnits.dbf}\\
+\hline
+xpKilled & Returns experience points granted for killing the unit. \texttt{XP\_KILLED} value from \texttt{GUnits.dbf}\\
+\hline
+hp & Returns unit's hit points. \texttt{HIT\_POINT} value from \texttt{GUnits.dbf}\\
+\hline
+armor & Returns unit's armor. \texttt{ARMOR} value from \texttt{GUnits.dbf}\\
+\hline
+regen & Returns unit's regen. \texttt{REGEN} value from \texttt{GUnits.dbf}\\
+\hline
+race & Returns unit's race. \texttt{ID} value from \texttt{LRace.dbf}. See \hyperref[RaceCategory]{Race enumeration} for all possible values\\
+\hline
+subrace & Returns unit's subrace. \texttt{ID} value from \texttt{LSubRace.dbf}. See \hyperref[SubraceCategory]{Subrace enumeration} for all possible values\\
+\hline
+small & Indicates if the unit is small (occupies single slot). \texttt{SIZE\_SMALL} value from \texttt{GUnits.dbf}\\
+\hline
+male & Indicates if the unit is male. \texttt{SEX\_M} value from \texttt{GUnits.dbf}\\
+\hline
+waterOnly & Indicates if the unit is water only. \texttt{WATER\_ONLY} value from \texttt{GUnits.dbf}\\
+\hline
+attacksTwice & Indicates if the unit attacks twice. \texttt{ATCK\_TWICE} value from \texttt{GUnits.dbf}\\
+\hline
+dynUpgLvl & Returns level after which \texttt{dynUpg2} rules are applied. \texttt{DYN\_UPG\_LV} from \texttt{GUnits.dbf}\\
+\hline
+dynUpg1 & Returns dynamic upgrade 1\\
+\hline
+dynUpg2 & Returns dynamic upgrade 2\\
+\hline
+attack1 & Returns primary \hyperref[Attack]{attack} or \texttt{nil} if no primary attack used\\
+\hline
+attack2 & Returns secondary \hyperref[Attack]{attack} or \texttt{nil} if no secondary attack used\\
+\hline
+altAttack & Returns alternative \hyperref[Attack]{attack} or \texttt{nil} if no alternative attack used\\
+\hline
+movement & Returns leader maximum movement points (or 0 if unit is not a leader)\\
+\hline
+scout & Returns leader scouting range (or 0 if unit is not a leader)\\
+\hline
+leadership & Returns current leadership value (or 0 if unit is not a leader)\\
+\hline
+\end{tabularx}
+\end{center}
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+hasAbility(Ability.OrbUse) & Returns \texttt{true} if leader has specified \hyperref[AbilityCategory]{ability} (or false if unit is not a leader)\\
+\hline
+hasMoveBonus(Ground.Water) & Returns \texttt{true} if leader has movement bonus on specified \hyperref[GroundCategory]{ground} (or false if unit is not a leader)\\
+\hline
+hasModifier('G000UM5021') & Returns \texttt{true} if the implementation has modifier specified by id or id string\\
+\hline
+getImmuneToAttackClass(Attack.Paralyze) & Returns \hyperref[ImmuneCategory]{immune type} for specified \hyperref[AttackCategory]{attack type}\\
+\hline
+getImmuneToAttackSource(Source.Water) & Returns \hyperref[ImmuneCategory]{immune type} for specified \hyperref[SourceCategory]{attack source type}\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/ApiDocs/en/unitslot.tex b/ApiDocs/en/unitslot.tex
new file mode 100644
index 00000000..500ec379
--- /dev/null
+++ b/ApiDocs/en/unitslot.tex
@@ -0,0 +1,62 @@
+\subsection{Unit slot}
+\label{UnitSlot}
+Represents one of the twelve unit slots on battlefield. Unit positions on a battlefield are mirrored. Frontline positions are even, backline - odd.
+\begin{center}
+\begin{tabular}{c c c}
+Attacker & & Defender\\
+% Attacker group represented as a table
+\begin{tabular}{| c | c |}
+\hline
+1 & 0\\
+\hline
+3 & 2\\
+\hline
+5 & 4\\
+\hline
+\end{tabular} &
+VS &
+% Defender group
+\begin{tabular}{| c | c |}
+\hline
+0 & 1\\
+\hline
+2 & 3\\
+\hline
+4 & 5\\
+\hline
+\end{tabular} \\
+
+\end{tabular}
+\end{center}
+
+\subsubsection{Properties}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+unit & Returns unit that occupies the slot or \texttt{nil} if slot is empty\\
+\hline
+position & Returns position of the slot (0-5)\\
+\hline
+line & Returns line index of the slot: 0 - frontline, 1 - backline\\
+\hline
+column & Returns column index of the slot: 0 - top, 1 - middle, 2 - bottom\\
+\hline
+frontline & Returns \texttt{true} if slot is on the frontline\\
+\hline
+backline & Returns \texttt{true} if slot is on the backline\\
+\hline
+\end{tabularx}
+\end{center}
+
+\subsubsection{Methods}
+\begin{center}
+\begin{tabularx}{\linewidth}{| l | X |}
+\hline
+\textbf{Name} & \textbf{Description} \\
+\hline
+distance(otherSlot) & Returns distance between two slots (used for adjacent slot calculations)\\
+\hline
+\end{tabularx}
+\end{center}
\ No newline at end of file
diff --git a/D2RSG b/D2RSG
index 01446029..63970135 160000
--- a/D2RSG
+++ b/D2RSG
@@ -1 +1 @@
-Subproject commit 01446029a7da5f0fafd4ee8507ced79e333cb0c4
+Subproject commit 63970135152b54a94a8e115135b737e0d26bf44f
diff --git a/Scripts/battleAi.lua b/Scripts/battleAi.lua
new file mode 100644
index 00000000..dbfccd75
--- /dev/null
+++ b/Scripts/battleAi.lua
@@ -0,0 +1,3092 @@
+-- Returns true if table 't' contains specified value
+function hasValue(t, value)
+ for index, v in ipairs(t) do
+ if v == value then
+ return true
+ end
+ end
+
+ return false
+end
+
+-- Returns true if unit is alive
+function isUnitAlive(unit)
+ return unit.hp > 0
+end
+
+-- Returns true if unit primary attack inflicts damage
+function isUnitAttackInflictsDamage(unit)
+ if not isUnitAlive(unit) then
+ return false
+ end
+
+ local impl = unit.impl
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ return attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+ or attackClass == Attack.Poison
+ or attackClass == Attack.Blister
+ or attackClass == Attack.Frostbite
+end
+
+-- Returns true if at least one unit from 'other group' is not always immune to attacks of units in 'group'
+function groupCanInflictDamageToOther(group, otherGroup)
+ local otherGroupUnits = otherGroup.units
+ if #otherGroupUnits <= 0 then
+ return true
+ end
+
+ local groupUnits = group.units
+ if #groupUnits <= 0 then
+ return false
+ end
+
+ for i = 1, #groupUnits do
+ local unit = groupUnits[i]
+ if isUnitAttackInflictsDamage(unit) then
+ local impl = unit.impl
+ local attack = impl.attack1
+ local attackClass = attack.type
+ local attackSource = attack.source
+
+ for j = 1, #otherGroupUnits do
+ local otherUnit = otherGroupUnits[j]
+ local otherUnitImpl = otherUnit.impl
+
+ if otherUnitImpl:getImmuneToAttackClass(attackClass) ~= Immune.Always
+ and otherUnitImpl:getImmuneToAttackSource(attackSource) ~= Immune.Always
+ then
+ return true
+ end
+ end
+ end
+ end
+
+ return false
+end
+
+function getCityProtection(group, ignoreCityProtection)
+ local groupId = group.id
+ local groupType = group.id.type
+ local fort = nil
+
+ if groupType == IdType.Stack then
+ fort = getScenario():getStack(groupId).inside
+ elseif groupType == IdType.Fortification then
+ fort = getScenario():getFort(groupId)
+ end
+
+ if fort == nil or ignoreCityProtection then
+ return 0
+ end
+
+ if fort.capital then
+ return getGlobal().variables.protCap
+ end
+
+ return getGlobal().variables:prot(fort.tier)
+end
+
+function computeImmuneCoefficient(impl)
+ local coeff = 0.0
+
+ if impl:getImmuneToAttackSource(Source.Weapon) ~= Immune.NotImmune then
+ coeff = coeff + 57.0
+ end
+
+ if impl:getImmuneToAttackSource(Source.Mind) ~= Immune.NotImmune then
+ coeff = coeff + 5.0
+ end
+
+ if impl:getImmuneToAttackSource(Source.Life) ~= Immune.NotImmune then
+ coeff = coeff + 6.0
+ end
+
+ if impl:getImmuneToAttackSource(Source.Death) ~= Immune.NotImmune then
+ coeff = coeff + 10.0
+ end
+
+ if impl:getImmuneToAttackSource(Source.Fire) ~= Immune.NotImmune then
+ coeff = coeff + 9.0
+ end
+
+ if impl:getImmuneToAttackSource(Source.Water) ~= Immune.NotImmune then
+ coeff = coeff + 2.0
+ end
+
+ if impl:getImmuneToAttackSource(Source.Air) ~= Immune.NotImmune then
+ coeff = coeff + 5.0
+ end
+
+ if impl:getImmuneToAttackSource(Source.Earth) ~= Immune.NotImmune then
+ coeff = coeff + 3.0
+ end
+
+ if impl:getImmuneToAttackClass(Attack.Poison) ~= Immune.NotImmune then
+ coeff = coeff + 5.0
+ end
+
+ return coeff
+end
+
+function computePowerCoefficient(impl)
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ if attackClass == Attack.Heal
+ or attackClass == Attack.BoostDamage
+ or attackClass == Attack.Cure
+ or attackClass == Attack.Summon
+ or attackClass == Attack.GiveAttack
+ or attackClass == Attack.Doppelganger
+ then
+ return 100.0
+ end
+
+ return attack.power
+end
+
+function computeAttackClassCoefficient(impl, ignoreCityProtection)
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ if attackClass == Attack.Paralyze or attackClass == Attack.Petrify then
+ return 30.0
+ end
+
+ if attackClass == Attack.Damage then
+ return 1.0
+ end
+
+ if attackClass == Attack.Drain then
+ return 1.5
+ end
+
+ if attackClass == Attack.Heal then
+ return 1.0
+ end
+
+ if attackClass == Attack.Fear then
+ return 30.0
+ end
+
+ if attackClass == Attack.BoostDamage or attackClass == Attack.BestowWards then
+ return 40.0
+ end
+
+ if attackClass == Attack.Shatter then
+ return 30.0
+ end
+
+ if attackClass == Attack.LowerDamage or attackClass == Attack.LowerInitiative then
+ return 40.0
+ end
+
+ if attackClass == Attack.DrainOverflow then
+ return 2.0
+ end
+
+ if attackClass == Attack.Summon then
+ return 200.0
+ end
+
+ if attackClass == Attack.DrainLevel then
+ return 100.0
+ end
+
+ if attackClass == Attack.GiveAttack then
+ return 50.0
+ end
+
+ if attackClass == Attack.Doppelganger then
+ if ignoreCityProtection == false then
+ return 200.0
+ end
+
+ return 100.0
+ end
+
+ if attackClass == Attack.TransformSelf then
+ return 100.0
+ end
+
+ if attackClass ~= Attack.TransformOther then
+ return 1.0
+ end
+
+ if attack.reach == Reach.All then
+ return 100.0
+ end
+
+ return 60.0
+end
+
+function computeAttackReachCoefficient(impl, enemiesCount)
+ local attack = impl.attack1
+ local reach = attack.reach
+
+ if reach == Reach.Adjacent then
+ return 1.0
+ end
+
+ if reach == Reach.Any then
+ return 1.5
+ end
+
+ if reach ~= Reach.All then
+ return 1.0
+ end
+
+ return (enemiesCount - 1) * 0.4 + 1.0
+end
+
+function getMaxDamage(unitOrItemId, impl)
+ local t = unitOrItemId.type
+
+ if t == IdType.UnitGlobal or t == IdType.UnitGenerated then
+ local id
+ if t == IdType.UnitGenerated then
+ id = impl.global.id
+ else
+ id = impl.id
+ end
+
+ local idx = id.typeIndex
+
+ -- Check IDs of leaders who can have 'Heavy Strike' ability
+ if idx == 0x19
+ or idx == 0x20
+ or idx == 0x44
+ or idx == 0x45
+ or idx == 0x47
+ or idx == 0x70
+ or idx == 0x71
+ or idx == 0x96
+ or idx == 0x8009
+ or idx == 0x8011
+ then
+ return getGame().unitMaxDamage + getGame().leaderAdditionalDamage
+ end
+ end
+
+ return getGame().unitMaxDamage
+end
+
+function getUnitOrItemMaxDamage(unitOrItemId)
+ if unitOrItemId.type ~= IdType.Unit then
+ return getMaxDamage(unitOrItemId, nil)
+ end
+
+ local unit = getScenario():getUnit(unitOrItemId)
+ local impl = unit.impl
+ return getMaxDamage(impl.id, impl)
+end
+
+function computeDamageDrainHealCoefficient(unit, impl)
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ if attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+ then
+ local damage = attack.damage
+ local maxDamage = getMaxDamage(unit.impl.id, unit.impl)
+
+ return math.min(damage, maxDamage)
+ elseif attackClass == Attack.Heal then
+ return attack.heal
+ else
+ return 1.0
+ end
+end
+
+function computeSecondAttackCoefficient(impl)
+ local attack = impl.attack2
+ if attack == nil then
+ return 1.0
+ end
+
+ local attackClass = attack.type
+
+ if attackClass == Attack.Cure then
+ return 1.0
+ end
+
+ if attackClass == Attack.Poison
+ or attackClass == Attack.Frostbite
+ or attackClass == Attack.Blister
+ then
+ return 1.15
+ end
+
+ if attackClass == Attack.Revive then
+ return 3.0
+ end
+
+ return 1.0
+end
+
+function computeUnitCoefficient(unit, ignoreCityProtection, battle, cityArmor, enemiesCount, a6)
+ if isUnitAlive(unit) == false and a6 == false then
+ return 0.0
+ end
+
+ local impl
+ if battle ~= nil and battle:getUnitStatus(unit.id, BattleStatus.Transform) then
+ impl = unit.original.impl
+ else
+ impl = unit.impl
+ end
+
+ local attackInfinite = impl.attack1.infinite
+ local hasSecondAttack = impl.attack2 ~= nil
+
+ local unitHp
+ if a6 then
+ unitHp = unit.hpMax
+ else
+ unitHp = unit.hp
+ end
+
+ local armor = impl.armor
+ local unitArmor = armor + cityArmor
+
+ local immuneCoeff = computeImmuneCoefficient(impl)
+ local powerCoeff = computePowerCoefficient(impl)
+ local attackClassCoeff = computeAttackClassCoefficient(impl, ignoreCityProtection)
+ local attackReachCoeff = computeAttackReachCoefficient(impl, enemiesCount)
+ local dmgDrainHealCoeff = computeDamageDrainHealCoefficient(unit, impl)
+ local unitMaxArmor = getGame().unitMaxArmor
+
+ if unitArmor > unitMaxArmor then
+ unitArmor = unitMaxArmor
+ end
+
+ local v17 = unitHp / (60.0 - 60.0 * unitArmor * 0.01)
+
+ local coeff = v17 / (unitHp / 60.0)
+ * (powerCoeff
+ * 0.01
+ * dmgDrainHealCoeff
+ * attackReachCoeff
+ * attackClassCoeff
+ * ((immuneCoeff * 0.01 + 1.0) * unitHp))
+ * 0.01;
+
+ if attackInfinite then
+ coeff = coeff * 1.25
+ elseif hasSecondAttack then
+ coeff = computeSecondAttackCoefficient(impl) * coeff
+ end
+
+ if impl.attacksTwice then
+ coeff = coeff * 2.0
+ end
+
+ return coeff
+end
+
+function computeGroupCoefficient(group, ignoreCityProtection, enemiesCount, battle, ignoreCityProtection2, a8)
+ local cityArmor = 0
+ if ignoreCityProtection2 == false then
+ cityArmor = getCityProtection(group, ignoreCityProtection)
+ end
+
+ local coeff = 0.0
+ local units = group.units
+ for i = 1, #units do
+ local unit = units[i]
+ if isUnitAlive(unit) then
+ coeff = coeff + computeUnitCoefficient(unit, ignoreCityProtection, battle, cityArmor, enemiesCount, a8)
+ end
+ end
+
+ return coeff
+end
+
+function sub_5DEE03(groupSubrace, otherGroup, ignoreCityProtection, position, enemiesCount, ignoreCityProtection2)
+ local totalCoeff = 0.0
+ local biggestStackCoeff = 0.0
+ local biggestStackCoeffId = nil
+
+ -- Check stacks on tiles in -10 .. +10 square around position
+ for x = position.x - 10, position.x + 10, 1 do
+ for y = position.y - 10, position.y + 10, 1 do
+ local stack = getScenario():getStack(x, y)
+ if stack ~= nil and groupSubrace ~= stack.subrace and stack.order ~= Order.Stand then
+ local stackCoeff = computeGroupCoefficient(stack.group, ignoreCityProtection, enemiesCount, nil, ignoreCityProtection2, false)
+ totalCoeff = totalCoeff + stackCoeff
+
+ if biggestStackCoeff < stackCoeff then
+ biggestStackCoeff = stackCoeff
+ biggestStackCoeffId = stack.id
+ end
+ end
+ end
+ end
+
+ if biggestStackCoeffId ~= nil and biggestStackCoeffId == otherGroup.id then
+ return 0.0
+ end
+
+ return totalCoeff
+end
+
+function sub_5DECF0(group, groupCoeff, otherGroup, otherGroupCoeff, nearbyStacksCoeff)
+ if not groupCanInflictDamageToOther(otherGroup, group) then
+ return 0.0
+ end
+
+ local v8 = 0.0
+ if groupCoeff >= otherGroupCoeff * 0.5 then
+ v8 = groupCoeff + nearbyStacksCoeff
+ else
+ if groupCoeff >= nearbyStacksCoeff then
+ v8 = groupCoeff
+ else
+ v8 = nearbyStacksCoeff
+ end
+ end
+
+ local coeff = otherGroupCoeff / v8 * 0.5
+
+ if coeff >= 1.0 then
+ return 1.0
+ end
+
+ return coeff
+end
+
+function sub_5DED63(group, otherGroup, battle, position, countNearbyStacks, ignoreCityProtection)
+ local unitsCount = #group.units
+ local otherGroupUnitsCount = #otherGroup.units
+
+ local groupCoeff = computeGroupCoefficient(group, true, otherGroupUnitsCount, battle, true, false)
+ local otherGroupCoeff = computeGroupCoefficient(otherGroup, false, unitsCount, battle, ignoreCityProtection, false)
+
+ local nearbyStacksCoeff = 0.0
+ if countNearbyStacks == true then
+ nearbyStacksCoeff = sub_5DEE03(group.subrace, otherGroup, false, position, unitsCount, ignoreCityProtection)
+ end
+
+ return sub_5DECF0(otherGroup, otherGroupCoeff, group, groupCoeff, nearbyStacksCoeff)
+end
+
+-- Returns distance between two points, length of a line segment connecting two points.
+function distance(a, b)
+ local x = b.x - a.x
+ local y = b.y - a.y
+ return math.sqrt(x * x + y * y)
+end
+
+function computeStackAndFortRelativeCoefficient(stack, enemyFort, battle, ignoreCityProtection)
+ if groupCanInflictDamageToOther(stack.group, enemyFort.group) == false then
+ return 0.0
+ end
+
+ local enemyCoeff = 0.0
+ local stackUnitsCount = #stack.group.units
+ local enemyStackUnitsCount = 0
+
+ local enemyFortVisitor = enemyFort.visitor
+ if enemyFortVisitor ~= nil then
+ local enemyStackGroup = enemyFortVisitor.group
+ enemyCoeff = computeGroupCoefficient(enemyStackGroup, false, stackUnitsCount, battle, ignoreCityProtection, false)
+ enemyStackUnitsCount = #enemyStackGroup.units
+ end
+
+ enemyCoeff = enemyCoeff + computeGroupCoefficient(enemyFort.group, false, stackUnitsCount, battle, ignoreCityProtection, false)
+
+ if enemyStackUnitsCount == 0 then
+ enemyStackUnitsCount = #enemyFort.group.units
+ end
+
+ local stackCoeff = computeGroupCoefficient(stack.group, true, enemyStackUnitsCount, battle, true, false)
+ local stackPosition = stack.position
+
+ local enemyFortEntrance = enemyFort.entrance
+ -- How is this possible, if stack is attacking the fort it means it stands next to its entrance
+ -- This is original game logic
+ if distance(stackPosition, enemyFortEntrance) > 10.0 then
+ -- Accumulate enemy coefficient from nearby stacks
+ -- that are not inside cities and with non-Stand orders
+ getScenario():forEachStack(function (currStack)
+ if currStack.subrace ~= enemyFort.subrace then
+ return
+ end
+
+ if currStack.order == Order.Stand then
+ return
+ end
+
+ if currStack.inside then
+ return
+ end
+
+ if distance(currStack.position, enemyFortEntrance) >= 8.0 then
+ return
+ end
+
+ local c = computeGroupCoefficient(currStack.group, false, stackUnitsCount, battle, false, false)
+ enemyCoeff = enemyCoeff + c
+ end)
+ end
+
+ local relativeCoeff = stackCoeff / enemyCoeff * 0.5
+ if relativeCoeff >= 1.0 then
+ return 1.0
+ end
+
+ return relativeCoeff
+end
+
+function computeGroupsRelativeCoefficient(battle, activeUnitGroup, enemyGroup)
+ -- Check if activeUnitGroup is not stack or enemyGroup is not fort
+ if activeUnitGroup.id.type ~= IdType.Stack or enemyGroup.id.type ~= IdType.Fortification then
+ local pos = Point.new(-1, -1)
+ return sub_5DED63(activeUnitGroup, enemyGroup, battle, pos, false, true)
+ else
+ -- Enemy group is a fort
+ local enemyFort = getScenario():getFort(enemyGroup.id)
+ -- Active unit group is a stack
+ local activeStack = getScenario():getStack(activeUnitGroup.id)
+ return computeStackAndFortRelativeCoefficient(activeStack, enemyFort, battle, true)
+ end
+end
+
+function isStackHaveMovement(group)
+ if group.id.type == IdType.Stack then
+ return getScenario():getStack(group.id).movement > 0
+ end
+
+ return false
+end
+
+-- Returns true if specified race is unplayable (can't be controlled by a human player)
+function isRaceUnplayable(race)
+ return race ~= Race.Human
+ and race ~= Race.Heretic
+ and race ~= Race.Undead
+ and race ~= Race.Dwarf
+ and race ~= Race.Elf
+end
+
+function checkGroupCanRetreat(possibleActions, activeUnit, activeUnitGroup, battle, ignoreUnplayableRaces, ignoreHumanPlayers)
+ if not hasValue(possibleActions, BattleAction.Retreat) then
+ -- Retreat is not a possible action. Group can't retreat
+ return false
+ end
+
+ -- Don't retreat if:
+ -- battle is over and healers have their last turn
+ -- battle has not started yet
+ if battle.afterBattle or battle.currentRound < 1 then
+ return false
+ end
+
+ local activePlayer = nil
+ if battle:isUnitAttacker(activeUnit) then
+ activePlayer = battle.attackerPlayer
+ else
+ activePlayer = battle.defenderPlayer
+ end
+
+ if not ignoreHumanPlayers then
+ if activePlayer.isHuman then
+ return false
+ end
+ end
+
+ if not ignoreUnplayableRaces then
+ if isRaceUnplayable(activePlayer.race) then
+ return false
+ end
+ end
+
+ assert(activeUnitGroup.id.type == IdType.Stack)
+
+ local stack = nil
+ if activeUnitGroup.id.type == IdType.Stack then
+ stack = getScenario():getStack(activeUnitGroup.id)
+
+ local leader = stack.leader
+ local unitType = leader.impl.type
+ if unitType == Unit.Summon or unitType == Unit.Noble then
+ -- Summons and thieves (nobles) never retreat from battle
+ return false
+ end
+ end
+
+ local order = stack.order
+
+ local enemyGroup = nil
+ if not battle:isUnitAttacker(activeUnit) then
+ enemyGroup = battle.attacker.group
+ else
+ enemyGroup = battle.defender
+ end
+
+ -- Allow retreat if:
+ -- group order is Normal or Roam
+ -- group order is Attack and we are not fighting with its target group
+ -- group defending and it's AI order is not Assist
+ if order == Order.Normal
+ or order == Order.Roam
+ or order == Order.Attack
+ and stack.orderTargetId ~= enemyGroup.id
+ then
+ if not battle:isUnitAttacker(activeUnit)
+ or stack.aiOrder ~= Order.Assist
+ then
+ return true
+ end
+ end
+
+ return false
+end
+
+function isAttackClassCanInflictDamage(attackClass)
+ return attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+ or attackClass == Attack.Summon
+ or attackClass == Attack.Poison
+ or attackClass == Attack.Blister
+ or attackClass == Attack.Frostbite
+end
+
+function getTargetGroupByAttackClass(attackClass, battle, allyGroup)
+ -- Check if attack class targets enemy group or allies
+ if attackClass == Attack.Paralyze
+ or attackClass == Attack.Petrify
+ or attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.Fear
+ or attackClass == Attack.LowerDamage
+ or attackClass == Attack.LowerInitiative
+ or attackClass == Attack.Poison
+ or attackClass == Attack.Frostbite
+ or attackClass == Attack.Blister
+ or attackClass == Attack.Shatter
+ or attackClass == Attack.DrainOverflow
+ or attackClass == Attack.DrainLevel
+ or attackClass == Attack.TransformOther
+ then
+ -- Attack targets enemy group
+ local attacker = battle.attacker.group
+ if attacker.id == allyGroup.id then
+ -- Attacker is an ally, so enemy is defender
+ return battle.defender
+ end
+
+ -- Enemy is attacker
+ return attacker
+ end
+
+ -- Attack targets allied group
+ return allyGroup
+end
+
+-- Returns true if units in target group are always immune to specified attack class and damage source
+function isTargetGroupAlwaysImmuneToAttackClassAndSource(attackClass, attackSource, allyGroup, attackTargetGroup, attackTargets)
+ if allyGroup.id == attackTargetGroup.id then
+ -- Game expects we can always apply attacks on units from allied group
+ return false
+ end
+
+ local slots = attackTargetGroup.slots
+ for i = 1, #attackTargets do
+ local target = attackTargets[i]
+ if target < 0 then
+ goto continue
+ end
+
+ -- +1 because of lua 1-based indexing
+ local slot = slots[target + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ local impl = unit.impl
+
+ local immuneToSource = impl:getImmuneToAttackSource(attackSource) == Immune.Always
+ local immuneToClass = impl:getImmuneToAttackClass(attackClass) == Immune.Always
+ if not immuneToSource and not immuneToClass then
+ return false
+ end
+
+ ::continue::
+ end
+
+ return true
+end
+
+-- Returns
+-- true/false indicating whether unit can attack or not
+-- possible attack targets for specified unit in battle (if unit can attack)
+function getUnitBattleAttackTargets(battle, unit)
+ if battle:getUnitStatus(unit.id, BattleStatus.Retreated)
+ or battle:getUnitStatus(unit.id, BattleStatus.Retreat)
+ then
+ return false, nil
+ end
+
+ local possibleActions
+ , attackTargetGroup
+ , attackPossibleTargets
+ , item1TargetGroup
+ , item1PossibleTargets
+ , item2TargetGroup
+ , item2PossibleTargets = battle:getUnitActions(unit)
+
+ if hasValue(possibleActions, BattleAction.UseItem) then
+ -- Unit can use items, thats considered enough
+ return true, attackPossibleTargets
+ end
+
+ if not hasValue(possibleActions, BattleAction.Attack) then
+ -- Unit can not attack
+ return false, nil
+ end
+
+ local impl = unit.impl
+ local attack = impl.attack1
+ local attackClass = attack.type
+ local attackSource = attack.source
+
+ local allyGroup = nil
+ if battle:isUnitAttacker(unit) then
+ allyGroup = battle.attacker.group
+ else
+ allyGroup = battle.defender
+ end
+
+ local targetGroup = getTargetGroupByAttackClass(attackClass, battle, allyGroup)
+
+ if not isTargetGroupAlwaysImmuneToAttackClassAndSource(attackClass, attackSource, allyGroup, targetGroup, attackPossibleTargets) then
+ return true, attackPossibleTargets
+ end
+
+ return false, nil
+end
+
+function groupCanPerformOffenceBattleActions(battle, group, enemyGroup)
+ local units = group.units
+ local unitCount = #units
+ if unitCount <= 0 then
+ return false
+ end
+
+ for i = 1, #units do
+ local unit = units[i]
+ if isUnitAlive(unit) then
+ local impl = unit.impl
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ if isAttackClassCanInflictDamage(attackClass) then
+ local canAttack, attackTargets = getUnitBattleAttackTargets(battle, unit)
+ if canAttack then
+ if attackClass ~= Attack.Summon then
+ local attackSource = attack.source
+ local enemySlots = enemyGroup.slots
+ local enemyIsNotImmune = false
+
+ for j = 1, #attackTargets do
+ local targetPosition = attackTargets[j]
+ -- +1 because of lua 1-based indexing
+ local slot = enemySlots[targetPosition + 1]
+ local enemyUnit = slot.unit
+ local enemyUnitImpl = enemyUnit.impl
+
+ if enemyUnitImpl:getImmuneToAttackClass(attackClass) ~= Immune.Always
+ and enemyUnitImpl:getImmuneToAttackSource(attackSource) ~= Immune.Always
+ then
+ enemyIsNotImmune = true
+ break
+ end
+ end
+
+ if enemyIsNotImmune then
+ return true
+ end
+ end
+ end
+ end
+ end
+ end
+
+ return false
+end
+
+-- Returns unit impl with respect to transform
+function getOriginalUnitImpl(unit)
+ local original = unit.original
+ if original ~= nil then
+ return original.impl
+ end
+
+ return unit.impl
+end
+
+-- Returns true if unit is a leader
+function isLeader(unit)
+ return getOriginalUnitImpl(unit).leaderType ~= -1
+end
+
+function isGroupHasLessThanTwoUnitsAlive(battle, group, checkNonBattlingUnits)
+ local units = group.units
+ if #units <= 0 then
+ return true
+ end
+
+ local aliveCount = 0
+
+ for i = 1, #units do
+ local unit = units[i]
+ if checkNonBattlingUnits == false
+ and (battle:getUnitStatus(unit.id, BattleStatus.Retreated)
+ or battle:getUnitStatus(unit.id, BattleStatus.Hidden))
+ then
+ -- Skip unit
+ else
+ if isUnitAlive(unit) then
+ aliveCount = aliveCount + 1
+ end
+ end
+
+ if aliveCount >= 2 then
+ return false
+ end
+ end
+
+ return true
+end
+
+-- Returns BattleAction
+function retreatGroupUnitsFromBattle(unit, group, battle)
+ -- If unit at back lane or it's a leader, retreat
+ if (group:getUnitPosition(unit) % 2 == 1) or isLeader(unit) then
+ return BattleAction.Retreat
+ end
+
+ -- Check alive defenders at front lane positions
+ local slots = group.slots
+ local defenders = false
+ for i = 1, #slots do
+ local slot = slots[i]
+ if slot.frontline then
+ local groupUnit = slot.unit
+ if groupUnit ~= nil then
+ if battle:getUnitStatus(groupUnit.id, BattleStatus.Defend)
+ and battle:getUnitStatus(groupUnit.id, BattleStatus.Dead) == false
+ then
+ defenders = true
+ break
+ end
+ end
+ end
+ end
+
+ if defenders == false then
+ -- No alive and defending units found in the front lane.
+ -- Check if group have less than 2 alive units
+ if isGroupHasLessThanTwoUnitsAlive(battle, group, false) then
+ return BattleAction.Auto
+ else
+ return BattleAction.Defend
+ end
+ end
+
+ return BattleAction.Retreat
+end
+
+-- Must return result (true or false), battleAction, target unit id, attacker unit id
+function checkGroupShouldRetreat(battle, possibleActions, activeUnit, activeUnitGroup, enemyGroup, activeUnitIsAttacker, relativeCoeff)
+ if battle.currentRound < 1 then
+ return false, BattleAction.Retreat, Id.emptyId(), Id.emptyId()
+ end
+
+ if battle:getRetreatStatus(activeUnitIsAttacker) == Retreat.NoRetreat then
+ if battle.decidedToRetreat == true and activeUnitIsAttacker == false then
+ if relativeCoeff < 0.3
+ and isStackHaveMovement(enemyGroup) == false
+ and checkGroupCanRetreat(possibleActions, activeUnit, activeUnitGroup, battle, false, false) == true
+ then
+ battle:setRetreatStatus(false, Retreat.CoverAndRetreat)
+ end
+
+ battle:setDecidedToRetreat()
+ end
+
+ if battle:getRetreatStatus(activeUnitIsAttacker) == Retreat.NoRetreat
+ and checkGroupCanRetreat(possibleActions, activeUnit, activeUnitGroup, battle, true, true) == true
+ and groupCanPerformOffenceBattleActions(battle, activeUnitGroup, enemyGroup) == false
+ then
+ battle:setRetreatStatus(activeUnitIsAttacker, Retreat.FullRetreat)
+ end
+ end
+
+ if battle:getRetreatStatus(activeUnitIsAttacker) == Retreat.FullRetreat then
+ return true, BattleAction.Retreat, activeUnit.id, activeUnit.id
+ end
+
+ if battle:getRetreatStatus(activeUnitIsAttacker) == Retreat.CoverAndRetreat then
+ local action = retreatGroupUnitsFromBattle(activeUnit, activeUnitGroup, battle)
+
+ return true, action, activeUnit.id, activeUnit.id
+ end
+
+ return false, BattleAction.Retreat, Id.emptyId(), Id.emptyId()
+end
+
+function computePercentage(percentage, value)
+ return percentage * value / 100
+end
+
+function computeDamageWithBuffs(attack, maxDamage, battle, unit, addRandomDamage, easyDifficulty)
+ local attackDamage = attack.damage
+ if attackDamage > maxDamage then
+ attackDamage = maxDamage
+ end
+
+ local variables = getGlobal().variables
+
+ local randomAdditionalDamage = 0
+ if addRandomDamage then
+ local batDamage = variables.batDamage
+
+ if easyDifficulty then
+ batDamage = batDamage * 2
+ end
+
+ randomAdditionalDamage = randomNumber(batDamage)
+ end
+
+ local damage = attackDamage + randomAdditionalDamage
+
+ if battle ~= nil then
+ if battle:getUnitStatus(unit.id, BattleStatus.BoostDamageLvl1) then
+ damage = damage + computePercentage(variables:batBoostd(1), attackDamage)
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.BoostDamageLvl2) then
+ damage = damage + computePercentage(variables:batBoostd(2), attackDamage)
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.BoostDamageLvl3) then
+ damage = damage + computePercentage(variables:batBoostd(3), attackDamage)
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.BoostDamageLvl4) then
+ damage = damage + computePercentage(variables:batBoostd(4), attackDamage)
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.LowerDamageLvl1) then
+ damage = damage - computePercentage(variables:batLowerd(1), attackDamage)
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.LowerDamageLvl2) then
+ damage = damage - computePercentage(variables:batLowerd(2), attackDamage)
+ end
+ end
+
+ if damage < getGame().unitMinDamage then
+ return getGame().unitMinDamage
+ end
+
+ if damage > maxDamage then
+ damage = maxDamage
+ end
+
+ return damage
+end
+
+function attackGetDamageWithBuffs(attack, maxDamage, battle, unit)
+ if not attack then
+ return 0
+ end
+
+ local attackClass = attack.type
+
+ if attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+ or attackClass == Attack.Poison
+ or attackClass == Attack.Blister
+ or attackClass == Attack.Frostbite
+ then
+ -- Attack inflicts damage, compute damage with buffs
+ return computeDamageWithBuffs(attack, maxDamage, battle, unit, false, false)
+ end
+
+ -- Attack does not inflict damage
+ return 0
+end
+
+function attackGetDamageWithBuffsCheckTransform(unit, battle)
+ local impl = unit.impl
+ local unitMaxDamage = getMaxDamage(impl.id, impl)
+
+ local attack = impl.attack1
+ if attack.type == Attack.TransformSelf then
+ return attackGetDamageWithBuffs(impl.altAttack, unitMaxDamage, battle, unit)
+ end
+
+ local attackDamage = attackGetDamageWithBuffs(attack, unitMaxDamage, battle, unit)
+ return attackGetDamageWithBuffs(impl.attack2, unitMaxDamage, battle, unit) + attackDamage
+end
+
+function isDamagingAttack(attack)
+ if not attack then
+ return false
+ end
+
+ local attackClass = attack.type
+ return attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+ or attackClass == Attack.Poison
+ or attackClass == Attack.Blister
+ or attackClass == Attack.Frostbite
+end
+
+function getSoldierAttackSource(impl)
+ local attack = impl.attack1
+ if isDamagingAttack(attack) then
+ return attack.source
+ end
+
+ local attack2 = impl.attack2
+ if isDamagingAttack(attack2) then
+ return attack2.source
+ end
+
+ local altAttack = impl.altAttack
+ if altAttack ~= nil and isDamagingAttack(altAttack) then
+ return altAttack.source
+ end
+
+ -- Unknown soldier attack source
+ return -1
+end
+
+function computeArmor(unit, battle)
+ local impl = unit.impl
+ local armor = impl.armor
+ local shattered = battle:getUnitShatteredArmor(unit)
+ local fortArmor = battle:getUnitFortificationArmor(unit)
+
+ armor = armor - shattered
+ if armor < fortArmor then
+ armor = fortArmor
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.Defend) then
+ local v1 = 100 - armor
+
+ local v2 = getGlobal().variables.defendBonus * 0.01 * v1
+
+ local v3 = armor + v2
+
+ if v3 > getGame().unitMaxArmor then
+ v3 = getGame().unitMaxArmor
+ end
+
+ armor = v3
+ end
+
+ return armor
+end
+
+function computeEffectiveHp(unit, battle)
+ if not isUnitAlive(unit) then
+ return false
+ end
+
+ local armor = computeArmor(unit, battle)
+ local hp = unit.hp
+
+ -- Vanilla formula for effective hp
+ return hp * armor / 100 + hp
+end
+
+function computeTargetUnitAiPriority(unit, battle, damageWithBuffs)
+ local impl = unit.impl
+ local bigUnit = impl.small == false
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ local attack2 = impl.attack2
+
+ if bigUnit or attack.reach == Reach.Adjacent or attackClass == Attack.BoostDamage then
+ local effectiveHp = computeEffectiveHp(unit, battle)
+ if effectiveHp > damageWithBuffs then
+ return 10000 - effectiveHp
+ else
+ return 10000 + effectiveHp
+ end
+ end
+
+ local unitValue = impl.xpKilled
+ if attackClass == Attack.Heal or (attack2 ~= nil and attack2.type == Attack.Heal) then
+ return 10000 + unitValue * 2
+ end
+
+ if attackClass == Attack.Paralyze or (attack2 ~= nil and attack2.type == Attack.Paralyze)
+ or attackClass == Attack.Petrify or (attack2 ~= nil and attack2.type == Attack.Petrify)
+ then
+ return 10000 + unitValue * 8
+ end
+
+ if attackClass == Attack.Summon or (attack2 ~= nil and attack2.type == Attack.Summon) then
+ return 10000 + unitValue * 10
+ end
+
+ if attackClass == Attack.TransformOther or (attack2 ~= nil and attack2.type == Attack.TransformOther) then
+ return 10000 + unitValue * 9
+ end
+
+ if attackClass == Attack.GiveAttack or (attack2 ~= nil and attack2.type == Attack.GiveAttack) then
+ return 10000 + unitValue * 3
+ end
+
+ return 10000 + unitValue
+end
+
+-- Returns unit that is selected as an attack target among possible targets or nil
+function selectAttackTarget(battle, damageWithBuffs, attackTargetGroup, attackPossibleTargets, attackSource)
+ local slots = attackTargetGroup.slots
+ local maxTargetValue = 0
+ local selectedUnit = nil
+
+ for i = 1, #attackPossibleTargets do
+ local attackTargetPosition = attackPossibleTargets[i]
+
+ if attackTargetPosition < 0 then
+ goto continue
+ end
+
+ -- +1 because of lua 1-based indexing
+ local slot = slots[attackTargetPosition + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ local currentHp = unit.hp
+ if not battle:getUnitStatus(unit.id, BattleStatus.Summon) and currentHp > 0 then
+ if currentHp <= damageWithBuffs and computeEffectiveHp(unit, battle) <= damageWithBuffs then
+ local impl = unit.impl
+ local immune = impl:getImmuneToAttackSource(attackSource)
+ local targetValue = computeTargetUnitAiPriority(unit, battle, damageWithBuffs)
+
+ if immune == Immune.Once
+ and battle:isUnitResistantToSource(unit, attackSource) then
+ -- Target unit has resistance to attack source, reduce its value
+ targetValue = targetValue * 0.69999999
+ end
+
+ if targetValue > maxTargetValue and immune ~= Immune.Always then
+ maxTargetValue = targetValue
+ selectedUnit = unit
+ end
+ end
+ end
+
+ ::continue::
+ end
+
+ return selectedUnit
+end
+
+-- Leader can use items only if it has no other target, 'item use' action is permitted and its items can target someone
+function canLeaderUseItemInBattle(selectedAttackTarget, possibleActions, item1PossibleTargets, item2PossibleTargets)
+ if selectedAttackTarget ~= nil then
+ return false
+ end
+
+ return hasValue(possibleActions, BattleAction.UseItem) and (#item1PossibleTargets or #item2PossibleTargets)
+end
+
+function sub_5D10F9(item)
+ if not item then
+ return 0
+ end
+
+ local base = item.base
+ local itemType = base.type
+
+ if itemType == Item.Talisman then
+ return 4
+ end
+
+ if itemType == Item.Orb then
+ return 3
+ end
+
+ if itemType == Item.PotionRevive then
+ return 2
+ end
+
+ if itemType == Item.PotionHeal then
+ return 1
+ end
+
+ return 0
+end
+
+function getLeaderArtifactAttack(unit, artifactIndex)
+ local stack = getScenario():findStackByUnit(unit)
+ if not stack then
+ return nil
+ end
+
+ if stack.leader.id ~= unit.id then
+ return nil
+ end
+
+ local artifact1 = stack:getEquippedItem(Equipment.Artifact1)
+ local art1Attack = nil
+ if artifact1 then
+ art1Attack = artifact1.base.attack
+ end
+
+ local artifact2 = stack:getEquippedItem(Equipment.Artifact2)
+ local art2Attack = nil
+ if artifact2 then
+ art2Attack = artifact2.base.attack
+ end
+
+ if art1Attack == nil then
+ art1Attack = art2Attack
+ art2Attack = nil
+ end
+
+ if artifactIndex == 1 then
+ if art1Attack then
+ return art1Attack
+ end
+
+ return art2Attack
+ end
+
+ if artifactIndex == 2 then
+ return art2Attack
+ end
+
+ -- Wrong index
+ return nil
+end
+
+-- Checks if attack is transform or doppelganger and returns their alternative attack
+-- Otherwise returns attack itself
+function getAttackWrToTransformDoppel(impl, first)
+ local attack = nil
+ if first then
+ attack = impl.attack1
+ else
+ attack = impl.attack2
+ end
+
+ local attackClass = attack.type
+
+ if attackClass == Attack.TransformSelf
+ or attackClass == Attack.Doppelganger
+ then
+ if first then
+ return impl.altAttack
+ else
+ return impl.altAttack2
+ end
+ end
+
+ return attack
+end
+
+-- Returns unit attacks and their numbers
+function getUnitAttacks(unitId, checkTransformedAttack)
+ local attacks = {}
+
+ local unit = getScenario():getUnit(unitId)
+ local impl = unit.impl
+
+ local attack = nil
+ if checkTransformedAttack then
+ attack = getAttackWrToTransformDoppel(impl, true)
+ else
+ attack = impl.attack1
+ end
+
+ table.insert(attacks, { attack, 0 })
+
+ if impl.attack2 then
+ local attack2 = nil
+
+ if checkTransformedAttack then
+ attack2 = getAttackWrToTransformDoppel(impl, false)
+ else
+ attack2 = impl.attack2
+ end
+
+ table.insert(attacks, { attack2, 1 })
+ end
+
+ local art1Attack = getLeaderArtifactAttack(unit, 1)
+ if art1Attack ~= nil then
+ table.insert(attacks, { art1Attack, 2 })
+ end
+
+ local art2Attack = getLeaderArtifactAttack(unit, 2)
+ if art2Attack ~= nil then
+ table.insert(attacks, { art2Attack, 2 })
+ end
+
+ return attacks
+end
+
+function getAttackById(id, attackNumber, checkTransformedAttack)
+ if id.type == IdType.Unit then
+ local attacks = getUnitAttacks(id, checkTransformedAttack)
+ if attackNumber > #attacks then
+ return nil
+ end
+
+ local pair = attacks[attackNumber]
+ return pair[0]
+ elseif id.type == IdType.Item then
+ local item = getScenario():getItem(id)
+ local itemBase = item.base
+ local itemType = itemBase.type
+
+ if itemType == Item.PotionBoost
+ or itemType == Item.PotionHeal
+ or itemType == Item.PotionRevive
+ or itemType == Item.PotionPermanent
+ then
+ return nil
+ end
+
+ return itemBase.attack
+ end
+
+ return nil
+end
+
+function getAttackByIdAndCheckTransformed(id, attackNumber)
+ return getAttackById(id, attackNumber, true)
+end
+
+function isUnitHasLessThanHalfHitPoints(unit)
+ return unit.hp / unit.hpMax <= 0.5
+end
+
+function getAttackingItemTargets(attack, itemTargetGroup, itemPossibleTargets)
+ local attackClass = attack.type
+
+ if attackClass == Attack.Summon then
+ -- Choose frontlane slots as summon item targets
+ local targets = {}
+
+ for i = 1, #itemPossibleTargets do
+ if itemPossibleTargets[i] % 2 == 0 then
+ table.insert(targets, itemPossibleTargets[i])
+ end
+ end
+
+ return targets
+ end
+
+ if attackClass == Attack.Heal then
+ -- Choose units that have less than 50% of hit points left
+ local targets = {}
+
+ local slots = itemTargetGroup.slots
+ for i = 1, #itemPossibleTargets do
+ local possibleTarget = itemPossibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local unit = slots[possibleTarget + 1]
+ if isUnitHasLessThanHalfHitPoints(unit) then
+ table.insert(targets, possibleTarget)
+ end
+ end
+
+ return targets
+ end
+
+ return itemPossibleTargets
+end
+
+function pickRandomIfEqual(v1, v2)
+ if v1 == v2 then
+ return randomNumber(2) == 1
+ end
+
+ return false
+end
+
+-- Returns true/false, targetId
+function findHealAttackTarget(battle, targetGroup, possibleTargets)
+ local primaryHealRatio = 1.0
+ local primaryTarget = nil
+
+ local secondaryHealRatio = 1.0
+ local secondaryTarget = nil
+
+ local slots = targetGroup.slots
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+
+ if not unit then goto continue end
+
+ local ratio = unit.hp / unit.hpMax
+
+ if not (ratio < primaryHealRatio or pickRandomIfEqual(ratio, primaryHealRatio)) then
+ goto continue
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.Retreat) then
+ if secondaryTarget and battle:getUnitStatus(secondaryTarget.id, BattleStatus.Summon) then
+ -- Prefer healing summoned units over retreating allies
+ goto continue
+ end
+ elseif battle:getUnitStatus(unit.id, BattleStatus.Summon) then
+ primaryTarget = unit
+ primaryHealRatio = ratio
+ goto continue
+ end
+
+ if ratio < secondaryHealRatio or pickRandomIfEqual(ratio, secondaryHealRatio) then
+ secondaryTarget = unit
+ secondaryHealRatio = ratio
+ end
+
+ ::continue::
+ end
+
+ local target = primaryTarget
+ if not target then
+ target = secondaryTarget
+ end
+
+ if not target then
+ return false, nil
+ end
+
+ return true, target.id
+end
+
+-- Returns true/false, targetId
+function findReviveAttackTarget(battle, targetGroup, possibleTargets)
+ local primaryTarget = nil
+ local secondaryTarget = nil
+ local maxRatio = 1.0
+ local maxXpKilled = 0
+
+ local slots = targetGroup.slots
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+
+ if not unit then goto continue end
+
+ if isUnitAlive(unit) then
+ if primaryTarget ~= nil then
+ goto continue
+ end
+
+ local ratio = unit.hp / unit.hpMax
+ if ratio >= maxRatio then
+ if not pickRandomIfEqual(ratio, maxRatio) then
+ goto continue
+ end
+
+ maxRatio = ratio
+ secondaryTarget = unit
+ end
+ else
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+
+ if xpKilled >= maxXpKilled or pickRandomIfEqual(xpKilled, maxXpKilled) then
+ primaryTarget = unit
+ maxXpKilled = xpKilled
+ end
+ end
+
+ ::continue::
+ end
+
+ local target = primaryTarget
+ if not target then
+ target = secondaryTarget
+ end
+
+ if not target then
+ return false, nil
+ end
+
+ return true, target.id
+end
+
+-- Returns true/false, targetId
+function findDamageAttackTargetsForAllReach(targetGroup, possibleTargets)
+ local possibleTarget = -1
+
+ for i=1, #possibleTargets do
+ possibleTarget = possibleTargets[i]
+ if possibleTarget >= 0 then
+ break
+ end
+ end
+
+ if possibleTarget < 0 then
+ return false, nil
+ end
+
+ local slots = targetGroup.slots
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+ if unit then
+ return true, unit.id
+ end
+
+ return true, Id.summonId(possibleTarget)
+end
+
+-- Returns true/false, targetId
+function findDamageAttackTargetWithAnyReach(targetGroup, possibleTargets, damage, battle, attackClass, attackSource, unitStatus)
+ local maxPriority = 0
+ local slots = targetGroup.slots
+ local targetUnitId = Id.emptyId()
+
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+
+ if possibleTarget < 0 then
+ goto continue
+ end
+
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ local canTargetUnit = false
+ if not unitStatus then
+ canTargetUnit = true
+ else
+ canTargetUnit = not battle:getUnitStatus(unit.id, unitStatus)
+ end
+
+ if canTargetUnit then
+ local impl = unit.impl
+
+ local sourceImmune = impl:getImmuneToAttackSource(attackSource)
+ local classImmune = impl:getImmuneToAttackClass(attackClass)
+
+ if sourceImmune ~= Immune.Always and classImmune ~= Immune.Always then
+ local priority = computeTargetUnitAiPriority(unit, battle, damage)
+
+ if (sourceImmune == Immune.Once and battle:isUnitResistantToSource(unit, attackSource))
+ or (classImmune == Immune.Once and battle:isUnitResistantToClass(unit, attackClass)) then
+ priority = priority * 0.69999999
+ end
+
+ if priority > maxPriority then
+ maxPriority = priority
+ targetUnitId = unit.id
+ end
+ end
+ end
+
+ ::continue::
+ end
+
+ return targetUnitId ~= Id.emptyId(), targetUnitId
+end
+
+-- Returns targetId
+function findDamageAttackTargetWithAdjacentReach(targetGroup, possibleTargets, battle, attackSource, attackClass)
+ local primaryTarget = Id.emptyId()
+ local nonSummonTarget = Id.emptyId()
+ local secondaryTarget = Id.emptyId()
+ -- These constants are taken directly from game
+ local minEffHp = 999999
+ local summonMinEffHp = 999999
+
+ local slots = targetGroup.slots
+
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+
+ if not unit then
+ goto continue
+ end
+
+ local isSummon = battle:getUnitStatus(unit.id, BattleStatus.Summon)
+ if isSummon and primaryTarget ~= Id.emptyId() then
+ -- Don't update primaryTarget
+ goto continue
+ end
+
+ local effectiveHp = computeEffectiveHp(unit, battle)
+ if effectiveHp > 0 and effectiveHp < minEffHp then
+ local impl = unit.impl
+
+ if impl:getImmuneToAttackSource(attackSource) ~= Immune.Always
+ and impl:getImmuneToAttackClass(attackClass) ~= Immune.Always then
+ if isSummon then
+ if effectiveHp < summonMinEffHp or pickRandomIfEqual(effectiveHp, summonMinEffHp) then
+ summonMinEffHp = effectiveHp
+ secondaryTarget = unit.id
+ end
+ else
+ minEffHp = effectiveHp
+ nonSummonTarget = unit.id
+ end
+ end
+ end
+
+ primaryTarget = nonSummonTarget
+ ::continue::
+ end
+
+ if primaryTarget == Id.emptyId() then
+ return secondaryTarget
+ end
+
+ return primaryTarget
+end
+
+-- Returns true/false, targetId
+function findDamageAttackTargetsForNonAllReach(attack, damage, targetGroup, possibleTargets, battle)
+ local reach = attack.reach
+ local attackSource = attack.source
+ local attackClass = attack.type
+
+ if reach ~= Reach.Adjacent then
+ return findDamageAttackTargetWithAnyReach(targetGroup, possibleTargets, damage, battle, attackClass, attackSource, nil)
+ end
+
+ local targetId = findDamageAttackTargetWithAdjacentReach(targetGroup, possibleTargets, battle, attackSource, attackClass)
+ return targetId ~= Id.emptyId(), targetId
+end
+
+-- Assumption: returns true if unit with Paralyze or Petrify attack should use Block action
+-- because there are no allied units that can pertorm offence battle actions
+function sub_5D02D5(attackClass, battle, enemyGroup)
+ if attackClass ~= Attack.Paralyze and attackClass ~= Attack.Petrify then
+ return false
+ end
+
+ local attacker = battle.attacker
+ local group = attacker.group
+ if group.id == enemyGroup.id then
+ group = battle.defender
+ end
+
+ if groupCanPerformOffenceBattleActions(battle, group, enemyGroup) then
+ return false
+ end
+
+ return true
+end
+
+function findParalyzeOrPetrifyAttackTarget(battle, targetGroup, possibleTargets, attackClass)
+ if sub_5D02D5(attackClass, battle, targetGroup) then
+ -- Do not search for target
+ return false, nil
+ end
+
+ local attackingUnit = nil
+ local nonAtackingUnit = nil
+ local retreatingUnit = nil
+ local attackingUnitMaxXpKilled = 0
+ local nonAttackingUnitMaxXpKilled = 0
+
+ local slots = targetGroup.slots
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+
+ if not unit then
+ goto continue
+ end
+
+ if not battle:getUnitStatus(unit.id, BattleStatus.Paralyze)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Petrify) then
+ if battle:getUnitStatus(unit.id, BattleStatus.Retreat) then
+ retreatingUnit = unit
+ else
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+ if xpKilled > attackingUnitMaxXpKilled then
+ if getUnitBattleAttackTargets(battle, unit, nil) then
+ attackingUnit = unit
+ attackingUnitMaxXpKilled = xpKilled
+ elseif xpKilled > nonAttackingUnitMaxXpKilled
+ or pickRandomIfEqual(xpKilled, nonAttackingUnitMaxXpKilled) then
+ nonAtackingUnit = unit
+ nonAttackingUnitMaxXpKilled = xpKilled
+ end
+ end
+ end
+ end
+
+ ::continue::
+ end
+
+ local targetUnit = attackingUnit
+ if not attackingUnit then
+ targetUnit = nonAtackingUnit
+
+ if not nonAtackingUnit then
+ targetUnit = retreatingUnit
+
+ if retreatingUnit ~= nil then
+ local attacker = battle.attacker
+ local group = attacker.group
+ if group.id == targetGroup.id then
+ group = battle.defender
+ end
+
+ if groupCanPerformOffenceBattleActions(battle, group, targetGroup) then
+ targetUnit = retreatingUnit
+ end
+ end
+ end
+ end
+
+ if not targetUnit then
+ return false, nil
+ end
+
+ return targetUnit.id ~= Id.emptyId(), targetUnit.id
+end
+
+-- Returns true/false, targetId
+function findDotAttackTarget(unitStatus, battle, unitOrItemId, targetGroup, possibleTargets)
+ local attack = nil
+ local damage = 0
+
+ if unitOrItemId.type == IdType.Unit then
+ local unit = getScenario():getUnit(unitOrItemId)
+ if not unit then
+ -- This should never happen
+ return false, nil
+ end
+
+ local impl = unit.impl
+ attack = impl.attack1
+ damage = attackGetDamageWithBuffsCheckTransform(unit, battle)
+ else
+ local item = getScenario():getItem(unitOrItemId)
+ if not item then
+ -- This should never happen
+ return false, nil
+ end
+
+ local itemBase = item.base
+ attack = itemBase.attack
+ damage = attack.damage
+ end
+
+ local attackSource = attack.source
+ local attackClass = attack.type
+
+ local ok, targetId = findDamageAttackTargetWithAnyReach(targetGroup, possibleTargets, damage, battle, attackClass, attackSource, unitStatus)
+ if ok then
+ return true, targetId
+ end
+
+ return findDamageAttackTargetWithAnyReach(targetGroup, possibleTargets, damage, battle, attackClass, attackSource, nil)
+end
+
+function checkImplHasDamageOrDrainOrDrainOverflowAttack( impl )
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ return attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+end
+
+-- Returns true/false, targetId
+function findBoostDamageAttackTarget(battle, targetGroup, possibleTargets)
+ local targetUnit = nil
+ local damagedTargetUnit = nil
+ local maxXpKilled = 0
+ local damagedUnitMaxXpKilled = 0
+
+ local slots = targetGroup.slots
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+
+ if not unit then
+ goto continue
+ end
+
+ local impl = unit.impl
+ if not battle:getUnitStatus(unit.id, BattleStatus.Retreat)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Paralyze)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Petrify)
+ and checkImplHasDamageOrDrainOrDrainOverflowAttack(impl) then
+ local xpKilled = impl.xpKilled
+ if xpKilled > maxXpKilled or pickRandomIfEqual(xpKilled, maxXpKilled) then
+ local hp = unit.hp
+ local hpMax = unit.hpMax
+
+ if hp / hpMax < 0.25 then
+ if xpKilled > damagedUnitMaxXpKilled
+ or pickRandomIfEqual(xpKilled, damagedUnitMaxXpKilled) then
+ damagedTargetUnit = unit
+ damagedUnitMaxXpKilled = xpKilled
+ end
+ else
+ targetUnit = unit
+ maxXpKilled = xpKilled
+ end
+ end
+ end
+
+ ::continue::
+ end
+
+ local target = targetUnit
+ if not target then
+ target = damagedTargetUnit
+ end
+
+ if not target then
+ return false, nil
+ end
+
+ return true, target.id
+end
+
+-- Priority of 1 considered top, 999 - lowest priority
+function getUnitCurePriority(unit, battle)
+ local id = unit.id
+
+ if battle:getUnitStatus(id, BattleStatus.Transform) then
+ return 1
+ end
+
+ if battle:getUnitStatus(id, BattleStatus.Paralyze)
+ or battle:getUnitStatus(id, BattleStatus.Petrify) then
+ return 2
+ end
+
+ if battle:getUnitStatus(id, BattleStatus.Poison) then
+ return 3
+ end
+
+ if battle:getUnitStatus(id, BattleStatus.Frostbite) then
+ return 4
+ end
+
+ if battle:getUnitStatus(id, BattleStatus.Blister) then
+ return 5
+ end
+
+ return 999
+end
+
+-- Returns true/false, targetId
+function findCureAttackTarget(battle, targetGroup, possibleTargets)
+ local primaryTarget = nil
+ local primaryTargetMinValue = 99
+ local primaryTargetMaxXpKilled = 0
+
+ local secondaryTarget = nil
+ local secondaryTargetMinValue = 99
+ local secondaryTargetMaxXpKilled = 0
+
+ local slots = targetGroup.slots
+
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+
+ if not unit then
+ goto continue
+ end
+
+ local value = getUnitCurePriority(unit, battle)
+ local retreating = battle:getUnitStatus(unit.id, BattleStatus.Retreat)
+
+ if retreating or battle:getUnitStatus(unit.id, BattleStatus.Summon) then
+ if not primaryTarget
+ and (not retreating
+ or not secondaryTarget
+ or not battle:getUnitStatus(secondaryTarget.id, BattleStatus.Summon))
+ and value <= secondaryTargetMinValue then
+ if value < secondaryTargetMinValue then
+ secondaryTargetMaxXpKilled = 0
+ end
+
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+ if xpKilled > secondaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, secondaryTargetMaxXpKilled) then
+ secondaryTarget = unit
+ secondaryTargetMinValue = value
+ secondaryTargetMaxXpKilled = xpKilled
+ end
+ end
+ elseif value <= primaryTargetMinValue then
+ if value < primaryTargetMinValue then
+ primaryTargetMaxXpKilled = 0
+ end
+
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+ if xpKilled > primaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, primaryTargetMaxXpKilled) then
+ primaryTarget = unit
+ primaryTargetMinValue = value
+ primaryTargetMaxXpKilled = xpKilled
+ end
+ end
+
+ ::continue::
+ end
+
+ local targetUnit = primaryTarget
+ if not targetUnit then
+ targetUnit = secondaryTarget
+ end
+
+ if not targetUnit then
+ return false, nil
+ end
+
+ return true, targetUnit.id
+end
+
+-- Returns true/false, targetId
+function findSummonAttackTarget(targetGroup, possibleTargets)
+ local slots = targetGroup.slots
+
+ -- Pick starting index in [0 : #possibleTargets) range
+ -- +1 to convert index to [1 : #possibleTargets] range because of lua 1-based indexing
+ local index = randomNumber(#possibleTargets) + 1
+ local initialIndex = index
+
+ -- Search starting from random index, prefer frontline targets, if possible.
+ -- If end of possibleTargets list reached, continue from beginning until starting index.
+ repeat
+ local target = possibleTargets[index]
+ if slots[target + 1].frontline then
+ break
+ end
+
+ index = index + 1
+ if index > #possibleTargets then
+ index = 1
+ end
+ until (index == initialIndex)
+
+ local possibleTarget = possibleTargets[index]
+
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+
+ local targetId
+ if unit then
+ targetId = unit.id
+ else
+ targetId = Id.summonId(possibleTarget)
+ end
+
+ return true, targetId
+end
+
+-- Returns true/false, targetId
+function findTransformOtherAttackTarget(battle, targetGroup, possibleTargets)
+ local primaryTarget = nil
+ local secondaryTarget = nil
+ local primaryTargetMaxXpKilled = 0
+ local secondaryTargetMaxXpKilled = 0
+
+ local slots = targetGroup.slots
+
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+
+ if not unit then
+ goto continue
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.Transform) then
+ -- Unit already transformed
+ goto continue
+ end
+
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+
+ if xpKilled > primaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, primaryTargetMaxXpKilled) then
+ local hp = unit.hp
+ local hpMax = unit.hpMax
+
+ if hp / hpMax <= 0.2
+ or battle:getUnitStatus(unit.id, BattleStatus.Retreat) then
+ if xpKilled > secondaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, secondaryTargetMaxXpKilled) then
+ secondaryTarget = unit
+ secondaryTargetMaxXpKilled = xpKilled
+ end
+ else
+ primaryTarget = unit
+ primaryTargetMaxXpKilled = xpKilled
+ end
+ end
+
+ ::continue::
+ end
+
+ local target = primaryTarget
+ if not target then
+ target = secondaryTarget
+ end
+
+ if not target then
+ return false, nil
+ end
+
+ return true, target.id
+end
+
+function getTargetUnit(targetPosition, targetGroup, enemyGroup)
+ if targetPosition >= 0 then
+ -- +1 because of lua 1-based indexing
+ local slot = targetGroup.slots[targetPosition + 1]
+ return slot.unit
+ end
+
+ -- +1 because of lua 1-based indexing
+ local slot = enemyGroup.slots[-(targetPosition + 1) + 1]
+ return slot.unit
+end
+
+function isAttackEffectiveAgainstGroup(attack, group)
+ local attackClass = attack.type
+ if not isAttackClassCanInflictDamage(attackClass) then
+ return true
+ end
+
+ local units = group.units
+ if #units <= 0 then
+ -- Empty group ?!
+ return false
+ end
+
+ local attackSource = attack.source
+ for i=1, #units do
+ local unit = units[i]
+ local impl = unit.impl
+ if impl:getImmuneToAttackSource(attackSource) ~= Immune.Always
+ and impl:getImmuneToAttackClass(attackClass) ~= Immune.Always then
+ return true
+ end
+ end
+
+ -- All units are always immune to attack
+ return false
+end
+
+-- Returns true/false, targetId
+function findDoppelgangerAttackTarget(unitOrItemId, battle, targetGroup, possibleTargets)
+ local unit = getScenario():getUnit(unitOrItemId)
+ if not unit then
+ -- This should never happen
+ return false, nil
+ end
+
+ local enemyGroup = battle.attacker.group
+ if targetGroup.id == enemyGroup.id then
+ enemyGroup = battle.defender
+ end
+
+ local unitPosition = targetGroup:getUnitPosition(unit)
+ local primaryTargetXpKilled = 0
+ local secondaryTargetXpKilled = 0
+ local primaryTarget = nil
+ local secondaryTarget = nil
+
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ local targetUnit = getTargetUnit(possibleTarget, targetGroup, enemyGroup)
+ if not targetUnit then
+ goto continue
+ end
+
+ local impl = targetUnit.impl
+ local attack = impl.attack1
+ if not isAttackEffectiveAgainstGroup(attack, enemyGroup) then
+ goto continue
+ end
+
+ local xpKilled = impl.xpKilled
+ if attack.melee == (unitPosition % 2 == 0) then
+ if xpKilled > primaryTargetXpKilled or pickRandomIfEqual(xpKilled, primaryTargetXpKilled) then
+ primaryTargetXpKilled = xpKilled
+ primaryTarget = targetUnit
+ end
+ elseif primaryTarget == nil then
+ if xpKilled > secondaryTargetXpKilled or pickRandomIfEqual(xpKilled, secondaryTargetXpKilled) then
+ secondaryTargetXpKilled = xpKilled
+ secondaryTarget = targetUnit
+ end
+ end
+
+ ::continue::
+ end
+
+ if primaryTarget then
+ return true, primaryTarget.id
+ end
+
+ if secondaryTarget then
+ return true, secondaryTarget.id
+ end
+
+ return false, nil
+end
+
+function countAliveUnitsInGroup(group)
+ local units = group.units
+ local aliveUnits = 0
+
+ for i=1, #units do
+ local unit = units[i]
+ if isUnitAlive(unit) then
+ aliveUnits = aliveUnits + 1
+ end
+ end
+
+ return aliveUnits
+end
+
+-- Returns true if specified unit can (and should) perform TransformSelf attack
+function isTransformSelfPossible(unit, battle)
+ if battle:getUnitStatus(unit.id, BattleStatus.TransformSelf) then
+ return false
+ end
+
+ local allyGroup = nil
+ local enemyGroup = nil
+ if battle:isUnitAttacker(unit) then
+ allyGroup = battle.attacker.group
+ enemyGroup = battle.defender
+ else
+ allyGroup = battle.defender
+ enemyGroup = battle.attacker.group
+ end
+
+ local slots = allyGroup.slots
+
+ -- +1 because of lua 1-based indexing
+ if allyGroup:getUnitPosition(unit) % 2 == 1
+ and (slots[0 + 1].unit or slots[2 + 1].unit or slots[4 + 1].unit) then
+ -- Do not transform self if we stand at the frontlane with allied units
+ return false
+ end
+
+ if unit.hp / unit.hpMax <= 0.25 then
+ -- Don not transform self if we have less than 25% of health
+ return false
+ end
+
+ -- Transform self only if there are less than 3 alive enemies
+ return countAliveUnitsInGroup(enemyGroup) < 3
+end
+
+-- Returns true/false, targetId
+function findTransformSelfAttackTarget(unitOrItemId, battle, targetGroup, possibleTargets)
+ if unitOrItemId.type == IdType.Unit then
+ local unit = getScenario():getUnit(unitOrItemId)
+ if not unit then
+ -- This should never happen
+ return false, nil
+ end
+
+ if isTransformSelfPossible(unit, battle) then
+ return true, unit.id
+ end
+
+ return false, nil
+ end
+
+ -- unitOrItemId belongs to item
+ local minXpKilled = 99999
+ local targetUnit = nil
+
+ local slots = targetGroup.slots
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ if isTransformSelfPossible(unit, battle) then
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+
+ if xpKilled < minXpKilled then
+ minXpKilled = xpKilled
+ targetUnit = unit
+ end
+ end
+
+ ::continue::
+ end
+
+ if targetUnit then
+ return true, targetUnit.id
+ end
+
+ return false, nil
+end
+
+-- Returns true/false, targetId
+function findDrainLevelAttackTarget(battle, targetGroup, possibleTargets)
+ local slots = targetGroup.slots
+
+ local primaryTarget = nil
+ local secondaryTarget = nil
+ local primaryTargetMaxXpKilled = 0
+ local secondaryTargetMaxXpKilled = 0
+ local maxLevel = 1
+
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ local impl = unit.impl
+ local level = impl.level
+ if level ~= 1 and level >= maxLevel then
+ if level > maxLevel then
+ primaryTargetMaxXpKilled = 0
+ end
+
+ maxLevel = level
+ local xpKilled = impl.xpKilled
+ if battle:getUnitStatus(unit.id, BattleStatus.Retreat) then
+ if xpKilled > secondaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, secondaryTargetMaxXpKilled) then
+ secondaryTarget = unit
+ secondaryTargetMaxXpKilled = xpKilled
+ end
+ elseif xpKilled > primaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, primaryTargetMaxXpKilled) then
+ primaryTarget = unit
+ primaryTargetMaxXpKilled = xpKilled
+ end
+ end
+
+ ::continue::
+ end
+
+ if primaryTarget then
+ return true, primaryTarget.id
+ end
+
+ if secondaryTarget then
+ return true, secondaryTarget.id
+ end
+
+ return false, nil
+end
+
+-- Returns true/false, targetId
+function findGiveAttackTarget(battle, targetGroup, possibleTargets)
+ local slots = targetGroup.slots
+
+ local primaryTarget = nil
+ local secondaryTarget = nil
+ local primaryTargetMaxXpKilled = 0
+ local secondaryTargetMaxXpKilled = 0
+ local secondaryTargetIsBooster = true
+
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ if not battle:getUnitStatus(unit.id, BattleStatus.Retreat)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Paralyze)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Petrify)
+ then
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ if attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+ then
+ if xpKilled > primaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, primaryTargetMaxXpKilled)
+ then
+ local canAttack, _ = getUnitBattleAttackTargets(battle, unit)
+ if canAttack then
+ primaryTarget = unit
+ primaryTargetMaxXpKilled = xpKilled
+ end
+ end
+ elseif not primaryTarget
+ and (xpKilled > primaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, primaryTargetMaxXpKilled))
+ then
+ local boost = attackClass == Attack.BoostDamage
+ if not boost or secondaryTargetIsBooster then
+ local canAttack, _ = getUnitBattleAttackTargets(battle, unit)
+ if canAttack then
+ secondaryTarget = unit
+ secondaryTargetMaxXpKilled = xpKilled
+ secondaryTargetIsBooster = boost
+ end
+ end
+ end
+ end
+
+ ::continue::
+ end
+
+ if primaryTarget then
+ return true, primaryTarget.id
+ end
+
+ if secondaryTarget then
+ return true, secondaryTarget.id
+ end
+
+ return false, nil
+end
+
+function checkBestowWardsAttackCanBePerformed(battle, unitOrItemId)
+ local group = nil
+
+ -- Strange: we accessing enemy group here ?
+ -- This is what original game logic does
+ if battle:isUnitAttacker(unitOrItemId) then
+ group = battle.defender
+ else
+ group = battle.attacker.group
+ end
+
+ local units = group.units
+ for i=1, #units do
+ local unit = units[i]
+
+ if not battle:getUnitStatus(unit.id, BattleStatus.Dead)
+ and not battle:getUnitStatus(unit.id, BattleStatus.XpCounted)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Retreated)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Retreat)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Hidden)
+ and not battle:getUnitStatus(unit.id, BattleStatus.Unsummoned)
+ then
+ -- At least one unit can be buffed with 'Bestow wards'
+ return true
+ end
+ end
+
+ -- No units can be buffed
+ return false
+end
+
+-- Returns true/false, targetId
+function findBestowWardsAttackTarget(battle, targetGroup, possibleTargets)
+ local primaryTarget = nil
+ local secondaryTarget = nil
+ local primaryTargetMaxXpKilled = 0
+ local secondaryTargetMaxXpKilled = 0
+
+ local slots = targetGroup.slots
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.Retreat)
+ or battle:getUnitStatus(unit.id, BattleStatus.Paralyze)
+ or battle:getUnitStatus(unit.id, BattleStatus.Petrify)
+ then
+ goto continue
+ end
+
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+ if xpKilled > primaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, primaryTargetMaxXpKilled)
+ then
+ if unit.hp / unit.hpMax < 0.25 then
+ if xpKilled > secondaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, secondaryTargetMaxXpKilled)
+ then
+ secondaryTarget = unit
+ secondaryTargetMaxXpKilled = xpKilled
+ end
+ else
+ primaryTarget = unit
+ primaryTargetMaxXpKilled = xpKilled
+ end
+ end
+
+ ::continue::
+ end
+
+ if primaryTarget then
+ return true, primaryTarget.id
+ end
+
+ if secondaryTarget then
+ return true, secondaryTarget.id
+ end
+
+ return false, nil
+end
+
+-- Returns true/false, targetId
+function findShatterAttackTarget(battle, targetGroup, possibleTargets)
+ local primaryTarget = nil
+ local secondaryTarget = nil
+ local primaryTargetMaxArmor = 0
+ local secondaryTargetMaxArmor = 0
+
+ local slots = targetGroup.slots
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.Retreat)
+ or battle:getUnitStatus(unit.id, BattleStatus.Paralyze)
+ or battle:getUnitStatus(unit.id, BattleStatus.Petrify)
+ then
+ goto continue
+ end
+
+ local impl = unit.impl
+ local armor = impl.armor
+ if armor > primaryTargetMaxArmor
+ or pickRandomIfEqual(armor, primaryTargetMaxArmor)
+ then
+ if unit.hp / unit.hpMax < 0.25 then
+ if armor > secondaryTargetMaxArmor
+ or pickRandomIfEqual(armor, secondaryTargetMaxArmor)
+ then
+ secondaryTarget = unit
+ secondaryTargetMaxArmor = armor
+ end
+ else
+ primaryTarget = unit
+ primaryTargetMaxArmor = armor
+ end
+ end
+
+ ::continue::
+ end
+
+ if primaryTarget then
+ return true, primaryTarget.id
+ end
+
+ if secondaryTarget then
+ return true, secondaryTarget.id
+ end
+
+ return false, nil
+end
+
+-- Returns true/false, targetId
+function findFearAttackTarget(battle, targetGroup, possibleTargets)
+ local primaryTarget = nil
+ local secondaryTarget = nil
+ local primaryTargetMaxXpKilled = 0
+ local secondaryTargetMaxXpKilled = 0
+
+ local slots = targetGroup.slots
+ for i=1, #possibleTargets do
+ local possibleTarget = possibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+ local unit = slot.unit
+ if not unit then
+ goto continue
+ end
+
+ if battle:getUnitStatus(unit.id, BattleStatus.Retreat) then
+ goto continue
+ end
+
+ local impl = unit.impl
+ local xpKilled = impl.xpKilled
+ if xpKilled > primaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, primaryTargetMaxXpKilled)
+ then
+ local canAttack, _ = getUnitBattleAttackTargets(battle, unit)
+ if canAttack then
+ primaryTarget = unit
+ primaryTargetMaxXpKilled = xpKilled
+ elseif xpKilled > secondaryTargetMaxXpKilled
+ or pickRandomIfEqual(xpKilled, secondaryTargetMaxXpKilled)
+ then
+ secondaryTarget = unit
+ secondaryTargetMaxXpKilled = xpKilled
+ end
+ end
+
+ ::continue::
+ end
+
+ if primaryTarget then
+ return true, primaryTarget.id
+ end
+
+ if secondaryTarget then
+ return true, secondaryTarget.id
+ end
+
+ return false, nil
+end
+
+-- Returns true/false, targetId
+function findAttackTarget(unitOrItemId, attack, targetGroup, possibleTargets, battle)
+ local attackClass = attack.type
+
+ if attackClass == Attack.Heal then
+ return findHealAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+ then
+ if attack.reach == Reach.All then
+ return findDamageAttackTargetsForAllReach(targetGroup, possibleTargets)
+ end
+
+ local maxDamage = getUnitOrItemMaxDamage(unitOrItemId)
+ local damage = math.min(maxDamage, attack.damage)
+ return findDamageAttackTargetsForNonAllReach(attack, damage, targetGroup, possibleTargets, battle)
+ end
+
+ if attackClass == Attack.Paralyze then
+ return findParalyzeOrPetrifyAttackTarget(battle, targetGroup, possibleTargets, attackClass)
+ end
+
+ if attackClass == Attack.Poison then
+ return findDotAttackTarget(BattleStatus.Poison, battle, unitOrItemId, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.BoostDamage then
+ return findBoostDamageAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Cure then
+ return findCureAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Summon then
+ return findSummonAttackTarget(targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.TransformOther then
+ return findTransformOtherAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Revive then
+ return findReviveAttackTarget(targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Doppelganger then
+ return findDoppelgangerAttackTarget(unitOrItemId, battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.TransformSelf then
+ return findTransformSelfAttackTarget(unitOrItemId, battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Petrify then
+ return findParalyzeOrPetrifyAttackTarget(battle, targetGroup, possibleTargets, attackClass)
+ end
+
+ if attackClass == Attack.DrainLevel then
+ return findDrainLevelAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.GiveAttack then
+ return findGiveAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Frostbite then
+ return findDotAttackTarget(BattleStatus.Frostbite, battle, unitOrItemId, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Blister then
+ return findDotAttackTarget(BattleStatus.Blister, battle, unitOrItemId, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.BestowWards then
+ local canAttack = checkBestowWardsAttackCanBePerformed(battle, unitOrItemId)
+ if not canAttack then
+ return findHealAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ return findBestowWardsAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Shatter then
+ return findShatterAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ if attackClass == Attack.Fear then
+ return findFearAttackTarget(battle, targetGroup, possibleTargets)
+ end
+
+ return false, nil
+end
+
+function getNonAttackingItemTargets(item, targetGroup, itemPossibleTargets)
+ local base = item.base
+ local itemType = base.type
+
+ if itemType == Item.PotionRevive then
+ local targets = {}
+ for i=1, #itemPossibleTargets do
+ if itemPossibleTargets[i] % 2 == 1 then
+ -- Prefer reviving frontlane units
+ table.insert(targets, itemPossibleTargets[i])
+ end
+ end
+
+ return targets
+ end
+
+ if itemType == Item.PotionHeal then
+ local slots = targetGroup.slots
+
+ local targets = {}
+ for i=1, #itemPossibleTargets do
+ local possibleTarget = itemPossibleTargets[i]
+ -- +1 because of lua 1-based indexing
+ local slot = slots[possibleTarget + 1]
+
+ local unit = slot.unit
+ if unit ~= nil and isUnitHasLessThanHalfHitPoints(unit) then
+ -- Prefer healing units that have 50% of hit points or less
+ table.insert(targets, possibleTarget)
+ end
+ end
+
+ return targets
+ end
+
+ return itemPossibleTargets
+end
+
+-- Returns true/false, targetId
+function findHealOrRevivePotionAttackTarget(item, battle, itemTargetGroup, itemPossibleTargets)
+ local base = item.base
+ local itemType = base.type
+
+ if itemType == Item.PotionHeal then
+ return findHealAttackTarget(battle, itemTargetGroup, itemPossibleTargets)
+ end
+
+ if itemType == Item.PotionRevive then
+ return findReviveAttackTarget(battle, itemTargetGroup, itemPossibleTargets)
+ end
+
+ return false, nil
+end
+
+-- Returns true/false, targetId, attackerId
+function findItemAttackTarget(activeUnit, item, itemTargetGroup, itemPossibleTargets, battle)
+ if #itemPossibleTargets <= 0 then
+ return false, nil, nil
+ end
+
+ local attackerId = item.id
+ local attack = getAttackByIdAndCheckTransformed(item.id, 1)
+ if attack then
+ local targets = getAttackingItemTargets(attack, itemTargetGroup, itemPossibleTargets)
+ if #targets then
+ local found, targetId = findAttackTarget(item.id, attack, itemTargetGroup, targets, battle)
+ return found, targetId, attackerId
+ end
+ else
+ local targets = getNonAttackingItemTargets(item, itemTargetGroup, itemPossibleTargets)
+ if #targets then
+ local found, targetId = findHealOrRevivePotionAttackTarget(item, battle, itemTargetGroup, targets)
+ return found, targetId, attackerId
+ end
+ end
+
+ return false, nil, nil
+end
+
+-- Returns true/false, targetId, attackerId
+function findEquipmentAttackTarget(activeUnit, activeUnitGroup, item1TargetGroup, item1PossibleTargets, item2TargetGroup, item2PossibleTargets, battle)
+ if activeUnitGroup.id.type ~= IdType.Stack then
+ return false, nil, nil
+ end
+
+ local stack = getScenario():getStack(activeUnitGroup.id)
+ if not stack then
+ -- This should never happen
+ return false, nil, nil
+ end
+
+ local item1 = stack:getEquippedItem(Equipment.Battle1)
+ local item1Value = sub_5D10F9(item1)
+
+ local item2 = stack:getEquippedItem(Equipment.Battle2)
+ local item2Value = sub_5D10F9(item2)
+
+ if item1Value < item2Value then
+ if item2Value > 0 then
+ local ok, targetId, attackerId = findItemAttackTarget(activeUnit, item2, item2TargetGroup, item2PossibleTargets, battle)
+ if ok then
+ return ok, targetId, attackerId
+ end
+ end
+
+ if item1Value <= 0 then
+ return false, nil, nil
+ end
+
+ return findItemAttackTarget(activeUnit, item1, item1TargetGroup, item1PossibleTargets, battle)
+ end
+
+ if item1Value > 0 then
+ local ok, targetId, attackerId = findItemAttackTarget(activeUnit, item1, item1TargetGroup, item1PossibleTargets, battle)
+ if ok then
+ return ok, targetId, attackerId
+ end
+ end
+
+ if item2Value > 0 then
+ local ok, targetId, attackerId = findItemAttackTarget(activeUnit, item2, item2TargetGroup, item2PossibleTargets, battle)
+ if ok then
+ return ok, targetId, attackerId
+ end
+ end
+
+ return false, nil, nil
+end
+
+function sub_5CFA6C(impl)
+ local attack1 = impl.attack1
+ local attack1Class = attack1.type
+
+ if attack1Class == Attack.Damage
+ or attack1Class == Attack.Drain
+ or attack1Class == Attack.DrainOverflow
+ then
+ local attack2 = impl.attack2
+ if not attack2 then
+ return true
+ end
+
+ local attack2Class = attack2.type
+
+ if attack2Class == Attack.LowerDamage
+ or attack2Class == Attack.LowerInitiative
+ or attack2Class == Attack.Damage
+ or attack2Class == Attack.Drain
+ or attack2Class == Attack.DrainOverflow
+ then
+ return true
+ end
+ end
+
+ return false
+end
+
+-- Returns true/false
+function sub_5CFB6C(impl, allyGroupId, targetGroupId)
+ local attack1 = impl.attack1
+ local attack1Class = attack1.type
+
+ if attack1Class ~= Attack.Doppelganger or allyGroupId == targetGroupId then
+ -- Attack is not Doppelganger or groups are the same
+ return false
+ end
+
+ return true
+end
+
+-- Returns secondary attack in case when primary attack has Damage, Drain or DrainOverflow attack class
+function tryGetDamageOrDrainSecondAttack(impl)
+ local attack = impl.attack1
+ local attackClass = attack.type
+
+ if attackClass == Attack.Damage
+ or attackClass == Attack.Drain
+ or attackClass == Attack.DrainOverflow
+ then
+ return impl.attack2
+ end
+
+ return attack
+end
+
+function getTransformSelfAltAttackOrSecondAttack(impl, attack)
+ local attack1 = impl.attack1
+
+ if attack1.type == Attack.TransformSelf then
+ return impl.altAttack
+ end
+
+ if attack and attack1.id == attack.id then
+ return impl.attack2
+ end
+
+ return attack1
+end
+
+-- Returns true/false, target id
+function sub_5D36BF(activeUnit, damage, attack2, altAttack, enemyGroup, possibleTargets, battle)
+ local ok, targetId = findAttackTarget(activeUnit.id, attack2, enemyGroup, possibleTargets, battle)
+ if ok then
+ return true, targetId
+ end
+
+ if not altAttack then
+ return false, nil
+ end
+
+ local attackClass = altAttack.type
+
+ if attackClass ~= Attack.Damage
+ and attackClass ~= Attack.Drain
+ and attackClass ~= Attack.DrainOverflow
+ then
+ return findAttackTarget(activeUnit.id, altAttack, enemyGroup, possibleTargets, battle)
+ end
+
+ if altAttack.reach ~= Reach.All then
+ return findDamageAttackTargetsForNonAllReach(altAttack, damage, enemyGroup, possibleTargets, battle)
+ end
+
+ local allyGroup = nil
+ if battle:isUnitAttacker(activeUnit) then
+ allyGroup = battle.attacker.group
+ else
+ allyGroup = battle.defender
+ end
+
+ if isTargetGroupAlwaysImmuneToAttackClassAndSource(attackClass, altAttack.source, allyGroup, enemyGroup, possibleTargets) then
+ return false, nil
+ end
+
+ return findDamageAttackTargetsForAllReach(enemyGroup, possibleTargets)
+end
+
+-- Returns BattleAction, target unit id, attacker unit id
+function chooseAction(
+ -- Battle state
+ battle,
+ -- Unit for which to choose action
+ activeUnit,
+ -- List of unit possible battle actions
+ possibleActions,
+ -- Group that is a target for a unit attack
+ attackTargetGroup,
+ -- List of unit attack targets, indices of attack target group slots.
+ attackPossibleTargets,
+ -- Group that is a target for a `Battle1` equipped item in case of leader unit or nil if unit is not leader or don't wear item.
+ item1TargetGroup,
+ -- List of `Battle1` equipped item targets, indices of group slots.
+ item1PossibleTargets,
+ -- Group that is a target for a `Battle2` equipped item in case of leader unit or nil if unit is not leader or don't wear item.
+ item2TargetGroup,
+ -- List of `Battle2` equipped item targets, indices of group slots.
+ item2PossibleTargets)
+
+ local activeUnitIsAttacker = battle:isUnitAttacker(activeUnit)
+
+ local activeUnitGroup = nil
+ local enemyGroup = nil
+
+ if activeUnitIsAttacker then
+ activeUnitGroup = battle.attacker.group
+ enemyGroup = battle.defender
+ else
+ enemyGroup = battle.attacker.group
+ activeUnitGroup = battle.defender
+ end
+
+ local unknownGroup = nil
+ if attackTargetGroup.id == enemyGroup.id then
+ unknownGroup = enemyGroup
+ else
+ unknownGroup = activeUnitGroup
+ end
+
+ local relativeCoeff = computeGroupsRelativeCoefficient(battle, activeUnitGroup, enemyGroup)
+
+ local ok,
+ action,
+ selectedTargetId,
+ selectedAttackerId = checkGroupShouldRetreat(battle, possibleActions, activeUnit, activeUnitGroup, enemyGroup, activeUnitIsAttacker, relativeCoeff)
+
+ if ok then
+ -- Decision to retreat was made
+ return action, selectedTargetId, selectedAttackerId
+ end
+
+ local activeUnitIsLeader = isLeader(activeUnit)
+ if activeUnitIsLeader then
+ if relativeCoeff <= 0.3
+ and checkGroupCanRetreat(possibleActions, activeUnit, activeUnitGroup, battle, false, false)
+ and not isGroupHasLessThanTwoUnitsAlive(battle, activeUnitGroup, true)
+ then
+ local shouldRetreat = true
+ if not activeUnitIsAttacker and isStackHaveMovement(enemyGroup) then
+ shouldRetreat = false
+ end
+
+ if shouldRetreat then
+ -- Leader should retreat
+ return BattleAction.Retreat, activeUnit.id, activeUnit.id
+ end
+ end
+ end
+
+ -- Check if we can select attack target
+ local selectedAttackTarget = nil
+ local damage = 0
+
+ if #attackPossibleTargets ~= 0 then
+ damage = attackGetDamageWithBuffsCheckTransform(activeUnit, battle)
+ if damage > 0 then
+ local attackSource = getSoldierAttackSource(activeUnit.impl)
+ selectedAttackTarget = selectAttackTarget(battle, damage, unknownGroup, attackPossibleTargets, attackSource)
+ end
+ end
+
+ -- Check leader and its items
+ if activeUnitIsLeader
+ and relativeCoeff <= 0.7
+ and canLeaderUseItemInBattle(selectedAttackTarget, possibleActions, item1PossibleTargets, item2PossibleTargets)
+ then
+ -- Leader can use item in battle
+ local ok,
+ selectedTargetId,
+ selectedAttackerId = findEquipmentAttackTarget(activeUnit, activeUnitGroup, item1TargetGroup, item1PossibleTargets, item2TargetGroup, item2PossibleTargets, battle)
+ if ok then
+ -- Use item
+ return BattleAction.UseItem, selectedTargetId, selectedAttackerId
+ end
+ end
+
+ -- Check if we should defend
+ if #attackPossibleTargets == 0
+ or not hasValue(possibleActions, BattleAction.Attack) then
+ -- Decision to defend was made
+ return BattleAction.Defend, activeUnit.id, activeUnit.id
+ end
+
+ -- Check if we could perform attack action
+ local v43 = true
+ local activeUnitImpl = activeUnit.impl
+ local attack = activeUnitImpl.attack1
+ if attack.reach == Reach.All then
+ local v1 = isTargetGroupAlwaysImmuneToAttackClassAndSource(attack.type, attack.source, activeUnitGroup, unknownGroup, attackPossibleTargets)
+ local v2 = sub_5D02D5(attack.type, battle, unknownGroup)
+ if not v1
+ and not v2
+ then
+ local ok, targetId = findDamageAttackTargetsForAllReach(unknownGroup, attackPossibleTargets)
+ if ok then
+ -- Attack with reach 'All'
+ return BattleAction.Attack, targetId, activeUnit.id
+ end
+ end
+
+ v43 = false
+ end
+
+ if v43 then
+ if selectedAttackTarget then
+ -- Attack selected target
+ return BattleAction.Attack, selectedAttackTarget.id, activeUnit.id
+ end
+
+ if sub_5CFA6C(activeUnitImpl)
+ or sub_5CFB6C(activeUnitImpl, activeUnitGroup.id, attackTargetGroup.id) then
+ local found, targetId = findDamageAttackTargetsForNonAllReach(attack, damage, unknownGroup, attackPossibleTargets, battle)
+ if found then
+ -- Attack with reaches other than 'All'
+ return BattleAction.Attack, targetId, activeUnit.id
+ end
+ else
+ local attack2 = tryGetDamageOrDrainSecondAttack(activeUnitImpl)
+ local altAttack = getTransformSelfAltAttackOrSecondAttack(activeUnitImpl, attack2)
+
+ local ok, targetId = sub_5D36BF(activeUnit, damage, attack2, altAttack, unknownGroup, attackPossibleTargets, battle)
+ if ok then
+ -- Attack considering secondary or alt. attacks
+ return BattleAction.Attack, targetId, activeUnit.id
+ end
+ end
+ end
+
+ -- Nothing to attack, defend
+ return BattleAction.Defend, activeUnit.id, activeUnit.id
+end
diff --git a/Scripts/settings.lua b/Scripts/settings.lua
index 7f81c395..c593154d 100644
--- a/Scripts/settings.lua
+++ b/Scripts/settings.lua
@@ -127,38 +127,38 @@ settings = {
outlineColor = {
red = 0, green = 0, blue = 0
},
-
- -- Movement cost on water tiles
- water = {
- -- Default movement cost
- default = 6,
- -- Movement cost for non water-only stacks with dead leader
- withDeadLeader = 12,
- -- Movement cost for stacks with water movement bonus
- withBonus = 2,
- -- Movement cost for water-only stacks
- waterOnly = 2,
- },
-
- -- Movement cost on forest tiles
- forest = {
- -- Default movement cost
- default = 4,
- -- Movement cost for stacks with dead leader
- withDeadLeader = 8,
- -- Movement cost for stacks with forest movement bonus
- withBonus = 2,
- },
-
- -- Movement cost on plain tiles
- plain = {
- -- Default movement cost
- default = 2,
- -- Movement cost for stacks with dead leader
- withDeadLeader = 4,
- -- Movement cost for stacks without plain movement bonus on road tiles
- onRoad = 1,
- },
+
+ -- Movement cost on water tiles
+ water = {
+ -- Default movement cost
+ default = 6,
+ -- Movement cost for non water-only stacks with dead leader
+ withDeadLeader = 12,
+ -- Movement cost for stacks with water movement bonus
+ withBonus = 2,
+ -- Movement cost for water-only stacks
+ waterOnly = 2,
+ },
+
+ -- Movement cost on forest tiles
+ forest = {
+ -- Default movement cost
+ default = 4,
+ -- Movement cost for stacks with dead leader
+ withDeadLeader = 8,
+ -- Movement cost for stacks with forest movement bonus
+ withBonus = 2,
+ },
+
+ -- Movement cost on plain tiles
+ plain = {
+ -- Default movement cost
+ default = 2,
+ -- Movement cost for stacks with dead leader
+ withDeadLeader = 4,
+ -- Movement cost for stacks without plain movement bonus on road tiles
+ onRoad = 1,
+ },
},
lobby = {
@@ -243,6 +243,11 @@ settings = {
carryXpOverUpgrade = false,
-- Allows units to receive multiple upgrades per single battle
allowMultiUpgrade = false,
+ -- Shows message box when error occurs in AI battle action script.
+ -- When disabled, error messages are silently written to error log
+ debugAi = false,
+ -- Fallback action for AI controlled units in case of script errors
+ fallbackAction = BattleAction.Defend,
},
-- Create mss32 proxy dll log files with debug info
diff --git a/Scripts/textids.lua b/Scripts/textids.lua
index a16be5a0..0406ccb9 100644
--- a/Scripts/textids.lua
+++ b/Scripts/textids.lua
@@ -253,20 +253,36 @@ textids = {
-- Fallback text "Wrong room password"
wrongRoomPassword = "",
},
-
- generator = {
- -- Description text for randomly generated scenarios
- -- Fallback text "Random scenario based on template '%TMPL%'. Seed: %SEED%. Starting gold: %GOLD%. Roads: %ROADS%%. Forest: %FOREST%%."
- description = "",
- -- Generator could not process game data from dbf tables or .ff files
- -- Error details are logged in mssProxyError.log
- -- Fallback text "Could not read game data needed for scenario generator.\nSee mssProxyError.log for details"
- wrongGameData = "",
- -- Error occured during scenario generation
- -- Fallback text "Error during random scenario map generation.\nSee mssProxyError.log for details".
- generationError = "",
- -- Generator failed to create scenario after specified number of attempts
- -- Fallback text "Could not generate scenario map after %NUM% attempts.\nPlease, adjust template contents or settings"
- limitExceeded = "",
- },
+
+ generator = {
+ -- Description text for randomly generated scenarios
+ -- Fallback text "Random scenario based on template '%TMPL%'. Seed: %SEED%. Starting gold: %GOLD%. Roads: %ROADS%%. Forest: %FOREST%%."
+ description = "",
+ -- Generator could not process game data from dbf tables or .ff files
+ -- Error details are logged in mssProxyError.log
+ -- Fallback text "Could not read game data needed for scenario generator.\nSee mssProxyError.log for details"
+ wrongGameData = "",
+ -- Error occured during scenario generation
+ -- Fallback text "Error during random scenario map generation.\nSee mssProxyError.log for details".
+ generationError = "",
+ -- Generator failed to create scenario after specified number of attempts
+ -- Fallback text "Could not generate scenario map after %NUM% attempts.\nPlease, adjust template contents or settings"
+ limitExceeded = "",
+ },
+
+ resourceMarket = {
+ -- Resource market site description for encyclopedia
+ -- Fallback text is "(Resource market)"
+ encyDesc = "",
+ -- Infinite amount of resources string.
+ -- Fallback text is "Inf."
+ infiniteAmount = "",
+ -- Exchange description for market window in game.
+ -- The text must contain keywords "%RES1%" and "%RES2%".
+ -- Fallback text is "You offer %RES1% to get %RES2% in return."
+ exchangeDesc = "",
+ -- Exchange is not available hint for market window in game.
+ -- Fallback text is "N/A"
+ exchangeNotAvailable = "",
+ }
}
diff --git a/luaApi.md b/luaApi.md
index c0a4fac1..0190e151 100644
--- a/luaApi.md
+++ b/luaApi.md
@@ -42,15 +42,33 @@ The function only accessible to scripts where scenario access is appropriate:
- `drainLevel.lua`
- custom attack reach scripts
- custom unit modifier script
+- AI battle actions script
`checkEventCondition` has `scenario` as its argument so `getScenario` is not bound to it.
```lua
getScenario():getUnit(unitId)
```
+##### randomNumber
+Generates random number in range \[0 : maxValue) using ingame generator
+```lua
+local n = randomNumber(100)
+```
+##### getGlobal
+Returns [global data storage](luaApi.md#global) used by game.
+```lua
+local data = getGlobal()
+local variables = data.variables
+```
+##### getGame
+Returns [game](luaApi.md#game) restrictions and constants.
+```lua
+getGame().unitMaxDamage
+```
---
#### Enumerations
+
##### Race
```lua
Race = { Human, Undead, Heretic, Dwarf, Neutral, Elf }
@@ -167,11 +185,94 @@ BattleStatus = {
}
```
+##### BattleAction
+```
+BattleAction = { Attack, Skip, Retreat, Wait, Defend, Auto, UseItem }
+```
+
+##### Retreat
+```
+Retreat = { NoRetreat, CoverAndRetreat, FullRetreat }
+```
+
##### Relation
```
Relation = { War, Neutral, Peace }
```
+##### Order
+```
+Order = { Normal, Stand, Guard, AttackStack, DefendStack, SecureCity,
+ Roam, MoveToLocation, DefendLocation, Bezerk, Assist, Steal, DefendCity }
+```
+
+##### IdType
+```
+IdType = {
+ Empty, -- Empty id
+ ApplicationText, -- Entries of TApp.dbf and TAppEdit.dbf
+ Building, -- Entries of GBuild.dbf
+ Race, -- Entries of GRace.dbf
+ Lord, -- Entries of GLord.dbf
+ Spell, -- Entries of GSpells.dbf
+ UnitGlobal, -- Unit implementations, entries of GUnits.dbf
+ UnitGenerated, -- Runtime-generated unit implementations
+ UnitModifier, -- Unit modifiers, entries of GModif.dbf
+ Attack, -- Attacks, entries of GAttacks.dbf
+ TextGlobal, -- Entries of TGlobal.dbf
+ LandmarkGlobal, -- Entries of GLmark.dbf
+ ItemGlobal, -- Base items, entries of GItem.dbf
+ NobleAction, -- Noble (thief) actions, entries of GAction.dbf
+ DynamicUpgrade, -- Dynamic upgrade rules, entries of GDynUpgr.dbf
+ DynamicAttack, -- Runtime-generated unit primary attacks
+ DynamicAltAttack, -- Runtime-generated unit primary alternative attacks
+ DynamicAttack2, -- Runtime-generated unit secondary attacks
+ DynamicAltAttack2, -- Runtime-generated unit secondary alternative attacks
+ CampaignFile, -- Campaign files
+ Plan, -- Utility for fast object lookup by map coordinates
+ ObjectCount, -- Number of objects in scenario file
+ ScenarioFile, -- Scenario files
+ Map, -- Scenario map
+ MapBlock, -- Blocks of scenario map
+ ScenarioInfo, -- Scenario information
+ SpellEffects,
+ Fortification, -- Capitals and villages
+ Player, -- Players in scenario
+ PlayerKnownSpells, -- Spells known by player in scenario
+ Fog, -- Fog of war for player in scenario
+ PlayerBuildings, -- Capital buildings
+ Road, -- Roads on scenario map
+ Stack, -- Stacks in scenario
+ Unit, -- Units in scenario
+ Landmark, -- Landmarks in scenario
+ Item, -- Items in scenario
+ Bag, -- Bags in scenario
+ Site, -- Sites in scenario
+ Ruin, -- Ruins in scenario
+ Tomb, -- Grave markers in scenario
+ Rod, -- Rods in scenario
+ Crystal, -- Gold mines and mana sources in scenario
+ Diplomacy, -- Diplomacy rules in scenario
+ SpellCast,
+ Location, -- Location on scenario map
+ StackTemplate, -- Stack templates in scenario
+ Event, -- Events in scenario
+ StackDestroyed, -- Information about stacks defeated in scenario
+ TalismanCharges, -- Talisman charges counter in scenario
+ Mountains, -- Mountains in scenario
+ SubRace, -- Subraces in scenario
+ SubRaceType, -- Entries of GSubRace.dbf
+ QuestLog, -- Scenario quest log
+ TurnSummary, -- Brief information about last turn in scenario
+ ScenarioVariable -- Scenario variables
+}
+```
+
+##### Resource
+```
+Resource = { Gold, InfernalMana, LifeMana, DeathMana, RunicMana, GroveMana }
+```
+
---
#### Point
@@ -214,6 +315,213 @@ id.value
-- Can be used as Lua table key for best performance.
id.typeIndex
```
+##### type
+Returns [type](luaApi.md#idtype) of identifier.
+Identifier type can help to distinguish one object from another.
+```lua
+id.type
+```
+##### summonId
+Creates special id for summoning units in battle using specified position in group.
+Position in group should be in \[0 : 5\] range.
+```lua
+Id.summonId(possibleTarget)
+```
+
+---
+
+#### Game
+Represents game restrictions and constants.
+Allows to access `settings.lua`.
+
+Methods:
+##### unitMaxDamage
+Maximum damage unit attack can inflict in battle. `unitMaxDamage` from `settings.lua`.
+```lua
+game.unitMaxDamage
+```
+##### unitMinDamage
+Minimum damage unit attack can inflict in battle. Currently 1.
+```lua
+game.unitMinDamage
+```
+##### unitMaxArmor
+Maximum armor unit can have. `unitMaxArmor` from `settings.lua`.
+```lua
+game.unitMaxArmor
+```
+##### leaderAdditionalDamage
+Additional damage granted by leader ability `Heavy strike`. Currently 100.
+```lua
+game.leaderAdditionalDamage
+```
+
+---
+
+#### Global
+Represents global data storage used by game.
+Allows to access contents of dbf files in 'Globals' folder of the game.
+
+Methods:
+##### variables
+Returns [global variables](luaApi.md#global-variables).
+```lua
+local v = getGlobal().variables
+```
+
+---
+
+#### Global Variables
+Allows to access contents of `GVars.dbf`.
+
+Methods:
+```lua
+-- Instructor skill bonus experience, 'WEAPN_MSTR'
+variables.weapnMstr
+-- Max additional initiative points that randomly added to unit in battle. 'BAT_INIT'
+variables.batInit
+-- Max additional damage points that randomly added to unit damage in battle. 'BAT_DAMAGE'
+variables.batDamage
+-- 'BAT_ROUND'
+variables.batRound
+-- 'BAT_BREAK'
+variables.batBreak
+-- 'BAT_BMODIF'
+variables.batBModif
+-- Initiative debuff. 'BATLOWERI'
+variables.batLoweri
+-- Maximum number of abilities leader can learn. 'LDRMAXABIL'
+variables.ldrMaxAbil
+-- Spy discovery chance per turn. 'SPY_DISCOV'
+variables.spyDiscov
+-- Damage from thief action 'poison city'. 'POISON_C'
+variables.poisonC
+-- Damage from thief action 'poison stack'. 'POISON_S'
+variables.poisonS
+-- Bribe multiplier. 'BRIBE'
+variables.bribe
+-- 'STEAL_RACE'
+variables.stealRace
+-- 'STEAL_NEUT'
+variables.stealNeut
+-- Minimal riot duration in days. 'RIOT_MIN'
+variables.riotMin
+-- Maximal riot duration in days. 'RIOT_MAX'
+variables.riotMax
+-- Percentage of riot damage. 'RIOT_DMG'
+variables.riotDmg
+-- Percentage of the original price of the items at sale. 'SELL_RATIO'
+variables.sellRatio
+-- Land transformation after city capture. 'T_CAPTURE'
+variables.tCapture
+-- Land transformation per turn by capital. 'T_CAPITAL'
+variables.tCapital
+-- Range of land transformation by rod per turn. 'ROD_RANGE'
+variables.rodRange
+-- Profit per mana crystal or gold mine per turn. 'CRYSTAL_P'
+variables.crystalP
+-- 'CONST_URG'
+variables.constUrg
+-- Bonus per day regeneration for fighter leader. 'REGEN_LWAR'
+variables.regenLwar
+-- Bonus per day regeneration for units in ruins. 'REGEN_RUIN'
+variables.regenRuin
+-- Diplomacy level representing peace. 'D_PEACE'
+variables.dPeace
+-- Diplomacy level representing war. 'D_WAR'
+variables.dWar
+-- Diplomacy level representing neutrality. 'D_NEUTRAL'
+variables.dNeutral
+-- 'D_GOLD'
+variables.dGold
+-- 'D_MK_ALLY'
+variables.dMkAlly
+-- 'D_ATTACK_SC'
+variables.dAttakSc
+-- 'D_ATTACK_FO'
+variables.dAttakFo
+-- 'D_ATTACK_PC'
+variables.dAttakPc
+-- 'D_ROD'
+variables.dRod
+-- 'D_REF_ALLY'
+variables.dRefAlly
+-- 'D_BK_ALLY'
+variables.dBkAlly
+-- 'D_NOBLE'
+variables.dNoble
+-- 'D_BKA_CHANCE'
+variables.dBkaChnc
+-- 'D_BKA_TURN'
+variables.dBkaTurn
+-- Capital protection. 'PROT_CAP'
+variables.protCap
+-- Additional gold on easy difficulty. 'BONUS_E'
+variables.bonusE
+-- Additional gold on average difficulty. 'BONUS_A'
+variables.bonusA
+-- Additional gold on hard difficulty. 'BONUS_H'
+variables.bonusH
+-- Additional gold on very hard difficulty. 'BONUS_V'
+variables.bonusV
+-- Income increase on easy difficulty. 'INCOME_E'
+variables.incomeE
+-- Income increase on average difficulty. 'INCOME_A'
+variables.incomeA
+-- Income increase on hard difficulty. 'INCOME_H'
+variables.incomeH
+-- Income increase on very hard difficulty. 'INCOME_V'
+variables.incomeV
+-- 'GU_RANGE'
+variables.guRange
+-- 'PA_RANGE'
+variables.paRange
+-- 'LO_RANGE'
+variables.loRange
+-- Armor bonus when unit uses defend in battle. 'DFENDBONUS'
+variables.defendBonus
+-- 'TALIS_CHRG'
+variables.talisChrg
+-- Chance to get spells with capture of a capital. 'GAIN_SPELL'
+variables.gainSpell
+```
+##### rodCost
+Rod placement [cost](luaApi.md#currency). `ROD_COST`
+```lua
+variables.rodCost
+```
+##### morale
+Input tier values must be in range \[1 : 6\]. `MORALE_n`
+```lua
+variables:morale(1)
+```
+##### batBoostd
+Damage boost values for various levels. Levels must be in range \[1 : 4\]. `BATBOOSTDn`
+```lua
+variables:batBoostd(4)
+```
+##### batLowerd
+Damage debuff values for various levels. Levels must be in range \[1 : 2\]. `BATLOWERDn`
+```lua
+variables:batLowerd(1)
+```
+##### tCity
+Land transformation per turn by cities of different tiers. Tier must be in range \[1 : 5\]. `T_CITYn`
+```lua
+variables:tCity(1)
+```
+##### prot
+City protection values for various tier levels. Tier must be in range \[1 : 6\]. In case of tier 6, returns `protCap`. `PROT_n`
+```lua
+variables:prot(3)
+```
+##### splPwr
+Input tier values must be in range \[1 : 5\]. `SPLPWR_n`
+```lua
+variables:splPwr(2)
+```
+
+---
#### Modifier
Represents unit modifier. Modifiers wrap [unit implementation](luaApi.md#unit-implementation).
@@ -483,6 +791,18 @@ Returns true if group has specified [unit](luaApi.md#unit-1) or [unit id](luaApi
group:hasUnit(unit)
group:hasUnit(Id.new('S143UN0001'))
```
+##### getUnitPosition
+Returns unit position in group, or -1 if unit not found.
+```lua
+group:getUnitPosition(unit)
+group:getUnitPosition(unit.id)
+```
+##### subrace
+Returns group [subrace](luaApi.md#subrace).
+In case of group inside [ruin](luaApi.md#ruin), returns -1 since ruins do not belong to subraces.
+```lua
+group.subrace
+```
---
@@ -594,6 +914,21 @@ Returns equipped [item](luaApi.md#item-2) by [equipment](luaApi.md#equipment) va
```lua
stack:getEquippedItem(Equipment.Boots)
```
+##### order
+Returns stack [order](luaApi.md#order).
+```lua
+stack.order
+```
+##### orderTargetId
+Returns stack's order target [id](luaApi.md#id).
+```lua
+stack.orderTargetId
+```
+##### aiOrder
+Returns stack [AI order](luaApi.md#order).
+```lua
+stack.aiOrder
+```
```lua
--- Returns stack current movement points.
stack.movement
@@ -620,6 +955,11 @@ Returns fort position as a [point](luaApi.md#point).
```lua
fort.position
```
+##### entrance
+Return fort entrance coordinates as a [point](luaApi.md#point).
+```lua
+fort.entrance
+```
##### owner
Returns [player](luaApi.md#player) that owns the fort. Neutral forts are owned by neutral player.
```lua
@@ -658,6 +998,121 @@ fort.tier
---
+#### Merchant item
+Represents item sold by [merchant](luaApi.md#merchant).
+
+Methods:
+##### base
+Returns [base item](luaApi.md#item-base).
+```lua
+merchantItem.base
+```
+##### amount
+Returns amount of items in merchant stock.
+```lua
+merchantItem.amount
+```
+
+---
+
+#### Merchant
+Represents Merchant on a map.
+
+Methods:
+##### id
+Returns merchant [id](luaApi.md#id). The value is unique for every merchant on scenario map.
+```lua
+merchant.id
+```
+##### position
+Returns merchant position as a [point](luaApi.md#point).
+```lua
+merchant.position
+```
+##### visitors
+Returns list of [players](luaApi.md#player) that have visited the merchant.
+```lua
+merchant.visitors
+```
+##### items
+Returns list of [merchant items](luaApi.md#merchant-item).
+```lua
+merchant.items
+```
+##### temple
+Returns true if merchant can be used as a temple for AI to heal.
+```lua
+merchant.temple
+```
+
+---
+
+#### Mercenary unit
+Represents unit for hire in mercenary camp.
+
+Methods:
+##### impl
+Returns [unit implementation](luaApi.md#unit-implementation).
+```lua
+mercenary.impl
+```
+##### unique
+Returns true is unit can be hired only once.
+```lua
+mercenaryUnit.unique
+```
+
+---
+
+#### Mercenary
+Represents Mercenary camp on a map.
+
+Methods:
+##### id
+Returns mercenary camp [id](luaApi.md#id). The value is unique for every mercenary camp on scenario map.
+```lua
+mercenary.id
+```
+##### position
+Returns mercenary camp position as a [point](luaApi.md#point).
+```lua
+mercenary.position
+```
+##### visitors
+Returns list of [players](luaApi.md#player) that have visited the mercenary camp.
+```lua
+mercenary.visitors
+```
+##### units
+Returns list of [mercenary units](luaApi.md#mercenary-unit).
+```lua
+mercenary.units
+```
+
+---
+
+#### Trainer
+Represents Trainer on a map.
+
+Methods:
+##### id
+Returns trainer [id](luaApi.md#id). The value is unique for every trainer on scenario map.
+```lua
+trainer.id
+```
+##### position
+Returns trainer position as a [point](luaApi.md#point).
+```lua
+trainer.position
+```
+##### visitors
+Returns list of [players](luaApi.md#player) that have visited the trainer.
+```lua
+trainer.visitors
+```
+
+---
+
#### Ruin
Represents Ruin on a map. Ruin contains a garrison [group](luaApi.md#group) of 6 [unit slots](luaApi.md#unit-slot).
@@ -717,6 +1172,28 @@ rod.owner
---
+#### Crystal
+Represents gold mine or mana source on a map.
+
+Methods:
+##### id
+Returns crystal [id](luaApi.md#id).
+```lua
+crystal.id
+```
+##### position
+Returns crystal position as a [point](luaApi.md#point).
+```lua
+crystal.position
+```
+##### resource
+Returns crystal [resource](luaApi.md#resource) type.
+```lua
+crystal.resource
+```
+
+---
+
#### Dynamic upgrade
Represents rules that applied when unit makes its progress gaining levels. Records in GDynUpgr.dbf are dynamic upgrades.
@@ -942,6 +1419,70 @@ if (unit == nil) then
return
end
```
+##### getItem
+Searches for [item](luaApi.md#item-2) by id string or item [id](luaApi.md#id), returns `nil` if not found.
+```lua
+local item = scenario:getItem('S143IM0001')
+if not item then
+ return
+end
+```
+##### getCrystal
+Searches for [crystal](luaApi.md#crystal) by:
+- id string
+- [id](luaApi.md#id)
+- pair of coordinates
+- [point](luaApi.md#point)
+
+Returns `nil` if not found.
+```lua
+local crystal = scenario:getCrystal('S143CR0003')
+if not crystal then
+ return
+end
+```
+##### getMerchant
+Searches for [merchant](luaApi.md#merchant) by:
+- id string
+- [id](luaApi.md#id)
+- pair of coordinates
+- [point](luaApi.md#point)
+
+Returns `nil` if not found.
+```lua
+local merchant = scenario:getMerchant('S143SI0001')
+if not merchant then
+ return
+end
+```
+##### getMercenary
+Searches for [mercenary camp](luaApi.md#mercenary) by:
+- id string
+- [id](luaApi.md#id)
+- pair of coordinates
+- [point](luaApi.md#point)
+
+Returns `nil` if not found.
+```lua
+local mercenary = scenario:getMercenary('S143SI0002')
+if not mercenary then
+ return
+end
+```
+##### getTrainer
+Searches for [trainer](luaApi.md#trainer) by:
+- id string
+- [id](luaApi.md#id)
+- pair of coordinates
+- [point](luaApi.md#point)
+
+Returns `nil` if not found.
+```lua
+local trainer = scenario:getTrainer('S143SI0005')
+if not trainer then
+ return
+end
+```
##### findStackByUnit
Searches for [stack](luaApi.md#stack) that has specified [unit](luaApi.md#unit-1) among all the stacks in the whole [scenario](luaApi.md#scenario).
You can also use unit id string or [id](luaApi.md#id).
@@ -976,6 +1517,26 @@ if ruin == nil then
end
```
**Note** that this search is heavy in terms of performance, so you probably want to minimize excessive calls and use variables to store its results.
+##### name
+Returns scenario name or empty string if scenario is unnamed.
+```lua
+scenario.name
+```
+##### description
+Returns scenario description or empty string if scenario has no description.
+```lua
+scenario.description
+```
+##### author
+Returns scenario author or empty string if no author specified.
+```lua
+scenario.author
+```
+##### seed
+Returns scenario initial seed used by random generator.
+```lua
+scenario.seed
+```
##### day
Returns number of current day in game.
```lua
@@ -995,6 +1556,83 @@ if diplomacy == nil then
return
end
```
+##### forEachStack
+Searches for every [stack](luaApi.md#stack) on a map and calls specified function on it.
+```lua
+scenario:forEachStack(function (stack)
+ log('Visit stack ' .. tostring(stack.id))
+end)
+```
+##### forEachLocation
+Searches for every [location](luaApi.md#location) on a map and calls specified function on it.
+```lua
+scenario:forEachLocation(function (location)
+ log('Visit location ' .. tostring(location.id))
+end)
+```
+##### forEachFort
+Searches for every [fort](luaApi.md#fort) on a map and calls specified function on it.
+```lua
+scenario:forEachFort(function (city)
+ log('Visit city ' .. tostring(city.id))
+end)
+```
+##### forEachRuin
+Searches for every [ruin](luaApi.md#ruin) on a map and calls specified function on it.
+```lua
+scenario:forEachRuin(function (ruin)
+ log('Visit ruin ' .. tostring(ruin.id))
+end)
+```
+##### forEachRod
+Searches for every [rod](luaApi.md#rod) on a map and calls specified function on it.
+```lua
+scenario:forEachRod(function (rod)
+ log('Visit rod ' .. tostring(rod.id))
+end)
+```
+##### forEachPlayer
+Searches for every [player](luaApi.md#player) on a map and calls specified function on it.
+```lua
+scenario:forEachPlayer(function (player)
+ log('Visit player ' .. tostring(player.id))
+end)
+```
+##### forEachUnit
+Searches for every [unit](luaApi.md#unit-1) on a map and calls specified function on it.
+```lua
+scenario:forEachUnit(function (unit)
+ log('Visit unit ' .. tostring(unit.id))
+end)
+```
+##### forEachCrystal
+Searches for every [crystal](luaApi.md#crystal) on a map and calls specified function on it.
+```lua
+scenario:forEachCrystal(function (crystal)
+ log('Visit crystal ' .. tostring(crystal.id))
+end)
+```
+##### forEachMerchant
+Searches for every [merchant](luaApi.md#merchant) on a map and calls specified function on it.
+```lua
+scenario:forEachMerchant(function (merchant)
+ log('Visit merchant ' .. tostring(merchant.id))
+end)
+```
+##### forEachMercenary
+Searches for every [mercenary camp](luaApi.md#mercenary) on a map and calls specified function on it.
+```lua
+scenario:forEachMercenary(function (mercenary)
+ log('Visit mercenary ' .. tostring(mercenary.id))
+end)
+```
+##### forEachTrainer
+Searches for every [trainer](luaApi.md#trainer) on a map and calls specified function on it.
+```lua
+scenario:forEachTrainer(function (trainer)
+ log('Visit trainer ' .. tostring(trainer.id))
+end)
+```
---
@@ -1159,6 +1797,24 @@ item.sellValue
---
+#### Battle Turn
+Represents unit action inside battle round.
+
+Methods:
+##### unit
+Returns [unit](luaApi.md#unit-1) that performs the turn.
+```lua
+turn.unit
+```
+##### attackCount
+Returns number of attacks unit can perform in its turn.
+Units with double attack (`turn.unit.impl.attacksTwice`) will have 2 in `attackCount`.
+```lua
+turn.attackCount
+```
+
+---
+
#### Battle
Represents battle information.
@@ -1180,13 +1836,19 @@ Returns true if autobattle mode is turned on.
```lua
battle.autoBattle
```
+##### fastBattle
+Returns true if fast battle is turned on.
+When fast battle is active, [auto battle](luaApi.md#autobattle) is active too.
+```lua
+battle.fastBattle
+```
##### attackerPlayer
-Returns [player](luaApi.md#player) that started battle.
+Returns [player](luaApi.md#player) that started battle or `nil` if not found.
```lua
battle.attackerPlayer
```
##### defenderPlayer
-Returns [player](luaApi.md#player) that was attacked.
+Returns [player](luaApi.md#player) that was attacked or `nil` if not found.
```lua
battle.defenderPlayer
```
@@ -1199,10 +1861,152 @@ battle.attacker
##### defender
Returns [group](luaApi.md#group) that was attacked.
Defender group can represent units of a [stack](luaApi.md#stack), [fort](luaApi.md#fort) or [ruin](luaApi.md#ruin).
-Use `group.id` to get actual type of a group.
+Use `group.id.type` to get actual type of a group.
```lua
battle.defender
```
+##### isUnitAttacker
+Returns true if [unit](luaApi.md#unit-1) belongs to attacker group.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:isUnitAttacker(unit)
+-- Same check with unit id
+battle:isUnitAttacker(unit.id)
+```
+##### getUnitActions
+Returns possible actions and attack options for specified [unit](luaApi.md#unit-1).
+Method also accepts unit [ids](luaApi.md#id).
+Returns:
+- list of [battle actions](luaApi.md#BattleAction) that unit can perform.
+- [group](luaApi.md#group) that is a target for a unit attack.
+- list of unit attack targets, indices of attack target group slots.
+- [group](luaApi.md#group) that is a target for a `Battle1` equipped item in case of leader unit.
+- list of `Battle1` equipped item targets, indices of group slots.
+- [group](luaApi.md#group) that is a target for a `Battle2` equipped item in case of leader unit.
+- list of `Battle2` equipped item targets, indices of group slots.
+```lua
+local actions,
+ attackTargetGroup,
+ attackTargets,
+ item1TargetGroup,
+ item1Targets,
+ item2TargetGroup,
+ item2Targets = battle:getUnitActions(unit)
+```
+##### getRetreatStatus
+Returns [retreat status](luaApi.md#Retreat) of attacker or defender group.
+```lua
+local attackerStatus = battle:getRetreatStatus(true)
+local defenderStatus = battle:getRetreatStatus(false)
+```
+##### decidedToRetreat
+Returns true if decision about groups retreat was made and should not be reconsidered.
+```lua
+battle.decidedToRetreat
+```
+##### afterBattle
+Returns true if battle is over but healers can make one more turn to heal allies.
+```lua
+battle.afterBattle
+```
+##### duel
+Returns true if battle is a duel between thief and a stack leader.
+All units except leaders are marked with `Hidden` [battle status](luaApi.md#battlestatus).
+```lua
+battle.duel
+```
+##### turnsOrder
+Returns list of [battle turns](luaApi.md#battle-turn) remaining in the current round of battle.
+Position of elements in the list corresponds to order of remaining turns in current round.
+In other words, at the start of a round, battle turns in the list are sorted according to units initiative, including an additional random initiative.
+```lua
+battle.turnsOrder
+```
+##### isUnitRevived
+Returns true if specified [unit](luaApi.md#unit-1) was revived during battle.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:isUnitRevived(unit)
+```
+##### isUnitWaiting
+Returns true if specified [unit](luaApi.md#unit-1) skipped its turn and waiting.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:isUnitWaiting(battle.attacker.leader)
+```
+##### getUnitShatteredArmor
+Returns amount of unit armor shattered in battle so far.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:getUnitShatteredArmor(unit)
+-- Same but using id
+battle:getUnitShatteredArmor(unit.id)
+```
+##### getUnitFortificationArmor
+Returns armor that is granted to unit by fortification, if any.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:getUnitFortificationArmor(unit)
+-- Same but using id
+battle:getUnitFortificationArmor(unit.id)
+```
+##### isUnitResistantToSource
+Returns true if specified unit is resistant to [attack source](luaApi.md#source).
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:isUnitResistantToSource(unit, attackSource)
+```
+##### isUnitResistantToClass
+Returns true if specified unit is resistant to [attack class](luaApi.md#attack).
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:isUnitResistantToClass(unit, attackSource)
+```
+##### getUnitDisableRound
+Returns round when paralyze, petrify or fear was applied to [unit](luaApi.md#unit-1). Returns 0 if unit is not disabled.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:getUnitDisableRound(unit.id)
+```
+##### getUnitPoisonRound
+Returns round when long poison was applied to [unit](luaApi.md#unit-1). Returns 0 if unit is not poisoned.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:getUnitPoisonRound(unit)
+```
+##### getUnitFrostbiteRound
+Returns round when long frostbite was applied to [unit](luaApi.md#unit-1). Returns 0 if unit is not frozen.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:getUnitFrostbiteRound(unit)
+```
+##### getUnitBlisterRound
+Returns round when long blister was applied to [unit](luaApi.md#unit-1). Returns 0 if unit is not burning.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:getUnitBlisterRound(unit)
+```
+##### getUnitTransformRound
+Returns round when long transform was applied to [unit](luaApi.md#unit-1). Returns 0 if unit is not transformed.
+Method also accepts unit [ids](luaApi.md#id).
+```lua
+battle:getUnitTransformRound(unit)
+```
+##### setRetreatStatus
+Sets [retreat status](luaApi.md#Retreat) of attacker or defender group.
+This method can be only used in AI battle action script.
+```lua
+-- Attacker group is going to fully retreat
+battle:setRetreatStatus(true, Retreat.FullRetreat)
+-- Do not retreat defender group
+battle:setRetreatStatus(false, Retreat.NoRetreat)
+```
+##### setDecidedToRetreat
+Notifies battle state that the decision about groups retreat was made and it is final.
+This method can be only used in AI battle action script.
+```lua
+battle:setDecidedToRetreat()
+```
---
diff --git a/mss32/include/aiattitudes.h b/mss32/include/aiattitudes.h
index 85ce08ea..64557836 100644
--- a/mss32/include/aiattitudes.h
+++ b/mss32/include/aiattitudes.h
@@ -20,7 +20,7 @@
#ifndef AIATTITUDES_H
#define AIATTITUDES_H
-#include "categories.h"
+#include "aiattitudescat.h"
#include "d2assert.h"
#include "textandid.h"
diff --git a/mss32/include/aiattitudescat.h b/mss32/include/aiattitudescat.h
new file mode 100644
index 00000000..ab81f25c
--- /dev/null
+++ b/mss32/include/aiattitudescat.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef AIATTITUDESCAT_H
+#define AIATTITUDESCAT_H
+
+#include "categories.h"
+
+namespace game {
+
+struct LAttitudesCategoryTable : CEnumConstantTable
+{ };
+
+struct LAttitudesCategory : public Category
+{ };
+
+namespace AttitudeCategories {
+
+struct Categories
+{
+ LAttitudesCategory* small;
+ LAttitudesCategory* medium;
+ LAttitudesCategory* large;
+ LAttitudesCategory* humongous;
+};
+
+Categories& get();
+
+} // namespace AttitudeCategories
+
+namespace LAttitudesCategoryTableApi {
+
+using Api = CategoryTableApi::Api;
+
+Api& get();
+
+} // namespace LAttitudesCategoryTableApi
+
+} // namespace game
+
+#endif // AIATTITUDESCAT_H
diff --git a/mss32/include/aiattitudestable.h b/mss32/include/aiattitudestable.h
new file mode 100644
index 00000000..bc97ae70
--- /dev/null
+++ b/mss32/include/aiattitudestable.h
@@ -0,0 +1,70 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef AIATTITUDESTABLE_H
+#define AIATTITUDESTABLE_H
+
+#include "aiattitudescat.h"
+#include "d2map.h"
+#include "smartptr.h"
+
+namespace game {
+
+struct CAiAttitudesTableVftable;
+struct CAiAttitudes;
+
+struct CAiAttitudesTableData
+{
+ Map> attitudes;
+};
+
+assert_size(CAiAttitudesTableData, 28);
+
+struct CAiAttitudesTable
+{
+ CAiAttitudesTableVftable* vftable;
+ CAiAttitudesTableData* data;
+};
+
+assert_size(CAiAttitudesTable, 8);
+
+struct CAiAttitudesTableVftable
+{
+ using Destructor = CAiAttitudesTable*(__thiscall*)(CAiAttitudesTable* thisptr, char flags);
+ Destructor destructor;
+};
+
+assert_vftable_size(CAiAttitudesTableVftable, 1);
+
+namespace CAiAttitudesTableApi {
+
+struct Api
+{
+ using Find = CAiAttitudes*(__thiscall*)(const CAiAttitudesTable* table,
+ const LAttitudesCategory* category);
+ Find find;
+};
+
+Api& get();
+
+} // namespace CAiAttitudesTableApi
+
+} // namespace game
+
+#endif // AIATTITUDESTABLE_H
diff --git a/mss32/include/aipriority.h b/mss32/include/aipriority.h
index 6b135bd9..ef13c4cc 100644
--- a/mss32/include/aipriority.h
+++ b/mss32/include/aipriority.h
@@ -20,14 +20,28 @@
#ifndef AIPRIORITY_H
#define AIPRIORITY_H
+#include "d2assert.h"
+
namespace game {
+struct IAiPriorityVftable;
+
struct IAiPriority
{
- const void* vftable;
+ const IAiPriorityVftable* vftable;
int priority;
};
+assert_size(IAiPriority, 8);
+
+struct IAiPriorityVftable
+{
+ using Destructor = void(__thiscall*)(IAiPriority* thisptr, char flags);
+ Destructor destructor;
+};
+
+assert_vftable_size(IAiPriorityVftable, 1);
+
} // namespace game
#endif // AIPRIORITY_H
diff --git a/mss32/include/attackutils.h b/mss32/include/attackutils.h
index bc83add1..1e3bd1ec 100644
--- a/mss32/include/attackutils.h
+++ b/mss32/include/attackutils.h
@@ -24,9 +24,12 @@ namespace game {
struct CMidgardID;
struct IAttack;
struct CAttackImpl;
+struct LAttackSource;
+struct LAttackClass;
enum class AttackClassId : int;
enum class AttackReachId : int;
+enum class AttackSourceId : int;
} // namespace game
namespace hooks {
@@ -61,6 +64,9 @@ bool isNormalDamageAttack(game::AttackClassId id);
/** Attack uses modifiable value of IAttack::getQtyDamage. */
bool isModifiableDamageAttack(game::AttackClassId id);
+const game::LAttackSource* getAttackSourceById(game::AttackSourceId id);
+const game::LAttackClass* getAttackClassById(game::AttackClassId id);
+
} // namespace hooks
#endif // ATTACKUTILS_H
diff --git a/mss32/include/battlemsgdata.h b/mss32/include/battlemsgdata.h
index 40cd2102..90b1eb9f 100644
--- a/mss32/include/battlemsgdata.h
+++ b/mss32/include/battlemsgdata.h
@@ -143,9 +143,9 @@ union UnitFlags
{
std::uint8_t indexInGroup : 3;
bool attacker : 1;
- std::uint8_t waited : 1;
+ bool waited : 1;
/** Performed first attack while attacking twice. */
- std::uint8_t attackedOnceOfTwice : 1;
+ bool attackedOnceOfTwice : 1;
bool revived : 1;
/**
* Indicates that unit waited and then started retreating.
@@ -297,7 +297,7 @@ struct BattleMsgData
* Original positions are saved and restored when duel ends.
* Non-leader units in both groups are marked with BattleStatus::Hidden.
*/
- bool duel;
+ std::uint8_t duel;
char padding2;
char unknown11[4];
};
@@ -307,6 +307,20 @@ assert_offset(BattleMsgData, turnsOrder, 3696);
assert_offset(BattleMsgData, attackerStackUnitIds, 3816);
assert_offset(BattleMsgData, battleStateFlags2, 3913);
+struct PossibleTargets
+{
+ const CMidgardID* attackTargetGroupId;
+ const TargetSet* attackTargets;
+
+ const CMidgardID* item1TargetGroupId;
+ const TargetSet* item1Targets;
+
+ const CMidgardID* item2TargetGroupId;
+ const TargetSet* item2Targets;
+};
+
+assert_size(PossibleTargets, 24);
+
namespace BattleMsgDataApi {
struct Api
@@ -402,7 +416,7 @@ struct Api
const CMidgardID* unitId);
CanPerformAttackOnUnitWithStatusCheck canPerformAttackOnUnitWithStatusCheck;
- using IsUnitAttackSourceWardRemoved = bool(__thiscall*)(BattleMsgData* thisptr,
+ using IsUnitAttackSourceWardRemoved = bool(__thiscall*)(const BattleMsgData* thisptr,
const CMidgardID* unitId,
const LAttackSource* attackSource);
IsUnitAttackSourceWardRemoved isUnitAttackSourceWardRemoved;
@@ -412,7 +426,7 @@ struct Api
const LAttackSource* attackSource);
RemoveUnitAttackSourceWard removeUnitAttackSourceWard;
- using IsUnitAttackClassWardRemoved = bool(__thiscall*)(BattleMsgData* thisptr,
+ using IsUnitAttackClassWardRemoved = bool(__thiscall*)(const BattleMsgData* thisptr,
const CMidgardID* unitId,
const LAttackClass* attackClass);
IsUnitAttackClassWardRemoved isUnitAttackClassWardRemoved;
@@ -489,6 +503,8 @@ struct Api
FindSpecificAttackTarget findBoostAttackTarget;
/** Used by AI to determine fear attack target. */
FindSpecificAttackTarget findFearAttackTarget;
+ /** Used by AI to determine heal attack target. */
+ FindSpecificAttackTarget findHealAttackTarget;
using FindDoppelgangerAttackTarget = bool(__stdcall*)(const IMidgardObjectMap* objectMap,
const CMidgardID* unitId,
@@ -652,6 +668,9 @@ struct Api
using IsAutoBattle = bool(__thiscall*)(const BattleMsgData* thisptr);
IsAutoBattle isAutoBattle;
+ using IsFastBattle = bool(__thiscall*)(const BattleMsgData* thisptr);
+ IsFastBattle isFastBattle;
+
using AlliesNotPreventingAdjacentAttack = bool(__stdcall*)(const BattleMsgData* battleMsgData,
const CMidUnitGroup* unitGroup,
int unitPosition,
@@ -693,6 +712,39 @@ struct Api
using BeforeBattleRound = void(__thiscall*)(BattleMsgData* thisptr);
BeforeBattleRound beforeBattleRound;
+
+ using AiChooseBattleAction = void(__stdcall*)(const IMidgardObjectMap* objectMap,
+ BattleMsgData* battleMsgData,
+ const CMidgardID* unitId,
+ const Set* possibleActions,
+ const PossibleTargets* possibleTargets,
+ BattleAction* battleAction,
+ CMidgardID* targetUnitId,
+ CMidgardID* attackerUnitId);
+ AiChooseBattleAction aiChooseBattleAction;
+
+ /** Returns retreat status for attacker or defender group. */
+ using GetRetreatStatus = RetreatStatus(__thiscall*)(const BattleMsgData* thisptr,
+ bool attacker);
+ GetRetreatStatus getRetreatStatus;
+
+ /** Sets retreat status for attacker or defender group. */
+ using SetRetreatStatus = void(__thiscall*)(BattleMsgData* thisptr,
+ bool attacker,
+ RetreatStatus status);
+ SetRetreatStatus setRetreatStatus;
+
+ /** Returns true if decision about group retreat was made and should not be reconsidered. */
+ using IsRetreatDecisionWasMade = bool(__thiscall*)(const BattleMsgData* thisptr);
+ IsRetreatDecisionWasMade isRetreatDecisionWasMade;
+
+ /** Sets flag to hint that retreat decision was made and should not be reconsidered. */
+ using SetRetreatDecisionWasMade = void(__thiscall*)(BattleMsgData* thisptr);
+ SetRetreatDecisionWasMade setRetreatDecisionWasMade;
+
+ /** Returns true if battle is over but healers can make one more turn. */
+ using IsAfterBattle = bool(__thiscall*)(const BattleMsgData* thisptr);
+ IsAfterBattle isAfterBattle;
};
Api& get();
diff --git a/mss32/include/battlemsgdatahooks.h b/mss32/include/battlemsgdatahooks.h
index 8067aa7c..77421bf7 100644
--- a/mss32/include/battlemsgdatahooks.h
+++ b/mss32/include/battlemsgdatahooks.h
@@ -27,6 +27,7 @@ namespace game {
struct CMidgardID;
struct BattleMsgData;
struct IMidgardObjectMap;
+struct PossibleTargets;
using TargetSet = Set;
using GroupIdTargetsPair = Pair;
@@ -66,6 +67,15 @@ void __stdcall updateBattleActionsHooked(const game::IMidgardObjectMap* objectMa
void __fastcall beforeBattleRoundHooked(game::BattleMsgData* thisptr, int /*%edx*/);
+void __stdcall aiChooseBattleActionHooked(const game::IMidgardObjectMap* objectMap,
+ game::BattleMsgData* battleMsgData,
+ const game::CMidgardID* unitId,
+ const game::Set* possibleActions,
+ const game::PossibleTargets* possibleTargets,
+ game::BattleAction* battleAction,
+ game::CMidgardID* targetUnitId,
+ game::CMidgardID* attackerUnitId);
+
} // namespace hooks
#endif // BATTLEMSGDATAHOOKS_H
diff --git a/mss32/include/bindings/battlemsgdataview.h b/mss32/include/bindings/battlemsgdataview.h
index 845b762a..cf508883 100644
--- a/mss32/include/bindings/battlemsgdataview.h
+++ b/mss32/include/bindings/battlemsgdataview.h
@@ -20,7 +20,10 @@
#ifndef BATTLEMSGDATAVIEW_H
#define BATTLEMSGDATAVIEW_H
+#include "unitview.h"
#include
+#include
+#include
namespace sol {
class state;
@@ -30,6 +33,8 @@ namespace game {
struct BattleMsgData;
struct IMidgardObjectMap;
struct CMidgardID;
+enum class BattleAction : int;
+enum class RetreatStatus : std::uint8_t;
} // namespace game
namespace bindings {
@@ -39,6 +44,24 @@ class PlayerView;
class StackView;
class GroupView;
+class BattleTurnView
+{
+public:
+ BattleTurnView(const game::CMidgardID& unitId,
+ char attackCount,
+ const game::IMidgardObjectMap* objectMap);
+
+ static void bind(sol::state& lua);
+
+ UnitView getUnit() const;
+ int getAttackCount() const;
+
+private:
+ game::CMidgardID unitId;
+ const game::IMidgardObjectMap* objectMap;
+ char attackCount;
+};
+
class BattleMsgDataView
{
public:
@@ -51,6 +74,7 @@ class BattleMsgDataView
int getCurrentRound() const;
bool getAutoBattle() const;
+ bool getFastBattle() const;
std::optional getAttackerPlayer() const;
std::optional getDefenderPlayer() const;
@@ -58,6 +82,120 @@ class BattleMsgDataView
std::optional getAttacker() const;
std::optional getDefender() const;
+ game::RetreatStatus getRetreatStatus(bool attacker) const;
+ bool isRetreatDecisionWasMade() const;
+
+ bool isUnitAttacker(const UnitView& unit) const;
+ bool isUnitAttackerId(const IdView& unitId) const;
+ bool isAfterBattle() const;
+ bool isDuel() const;
+
+ using UnitActions = std::tuple, // Possible unit actions
+ std::optional, // Attack target group
+ std::vector, // Attack targets
+ std::optional, // Item 1 target group
+ std::vector, // Item 1 targets
+ std::optional, // Item 2 target group
+ std::vector>; // Item 2 targets
+
+ UnitActions getUnitActions(const UnitView& unit) const;
+ UnitActions getUnitActionsById(const IdView& unitId) const;
+
+ int getUnitShatteredArmor(const UnitView& unit) const;
+ int getUnitShatteredArmorById(const IdView& unitId) const;
+
+ int getUnitFortificationArmor(const UnitView& unit) const;
+ int getUnitFortificationArmorById(const IdView& unitId) const;
+
+ bool isUnitResistantToSource(const UnitView& unit, int sourceId) const;
+ bool isUnitResistantToSourceById(const IdView& unitId, int sourceId) const;
+
+ bool isUnitResistantToClass(const UnitView& unit, int classId) const;
+ bool isUnitResistantToClassById(const IdView& unitId, int classId) const;
+
+ std::vector getTurnsOrder() const;
+
+ bool isUnitRevived(const UnitView& unit) const;
+ bool isUnitRevivedById(const IdView& unitId) const;
+
+ bool isUnitWaiting(const UnitView& unit) const;
+ bool isUnitWaitingById(const IdView& unitId) const;
+
+ int getUnitDisableRound(const UnitView& unit) const;
+ int getUnitDisableRoundById(const IdView& unitId) const;
+
+ int getUnitPoisonRound(const UnitView& unit) const;
+ int getUnitPoisonRoundById(const IdView& unitId) const;
+
+ int getUnitFrostbiteRound(const UnitView& unit) const;
+ int getUnitFrostbiteRoundById(const IdView& unitId) const;
+
+ int getUnitBlisterRound(const UnitView& unit) const;
+ int getUnitBlisterRoundById(const IdView& unitId) const;
+
+ int getUnitTransformRound(const UnitView& unit) const;
+ int getUnitTransformRoundById(const IdView& unitId) const;
+
+protected:
+ template
+ static void bindAccessMethods(T& view)
+ {
+ view["getUnitStatus"] = &BattleMsgDataView::getUnitStatus;
+ view["currentRound"] = sol::property(&BattleMsgDataView::getCurrentRound);
+ view["autoBattle"] = sol::property(&BattleMsgDataView::getAutoBattle);
+ view["fastBattle"] = sol::property(&BattleMsgDataView::getFastBattle);
+ view["attackerPlayer"] = sol::property(&BattleMsgDataView::getAttackerPlayer);
+ view["defenderPlayer"] = sol::property(&BattleMsgDataView::getDefenderPlayer);
+ view["attacker"] = sol::property(&BattleMsgDataView::getAttacker);
+ view["defender"] = sol::property(&BattleMsgDataView::getDefender);
+ view["isUnitAttacker"] = sol::overload<>(&BattleMsgDataView::isUnitAttacker,
+ &BattleMsgDataView::isUnitAttackerId);
+ view["getUnitActions"] = sol::overload<>(&BattleMsgDataView::getUnitActions,
+ &BattleMsgDataView::getUnitActionsById);
+ view["getRetreatStatus"] = &BattleMsgDataView::getRetreatStatus;
+ view["decidedToRetreat"] = sol::property(&BattleMsgDataView::isRetreatDecisionWasMade);
+ view["afterBattle"] = sol::property(&BattleMsgDataView::isAfterBattle);
+ view["duel"] = sol::property(&BattleMsgDataView::isDuel);
+ view["turnsOrder"] = sol::property(&BattleMsgDataView::getTurnsOrder);
+ view["isUnitRevived"] = sol::overload<>(&BattleMsgDataView::isUnitRevived,
+ &BattleMsgDataView::isUnitRevivedById);
+ view["isUnitWaiting"] = sol::overload<>(&BattleMsgDataView::isUnitWaiting,
+ &BattleMsgDataView::isUnitWaitingById);
+
+ view["getUnitShatteredArmor"] = sol::overload<>(
+ &BattleMsgDataView::getUnitShatteredArmor,
+ &BattleMsgDataView::getUnitShatteredArmorById);
+
+ view["getUnitFortificationArmor"] = sol::overload<>(
+ &BattleMsgDataView::getUnitFortificationArmor,
+ &BattleMsgDataView::getUnitFortificationArmorById);
+
+ view["isUnitResistantToSource"] = sol::overload<>(
+ &BattleMsgDataView::isUnitResistantToSource,
+ &BattleMsgDataView::isUnitResistantToSourceById);
+
+ view["isUnitResistantToClass"] = sol::overload<>(
+ &BattleMsgDataView::isUnitResistantToClass,
+ &BattleMsgDataView::isUnitResistantToClassById);
+
+ view["getUnitDisableRound"] = sol::overload<>(&BattleMsgDataView::getUnitDisableRound,
+ &BattleMsgDataView::getUnitDisableRoundById);
+
+ view["getUnitPoisonRound"] = sol::overload<>(&BattleMsgDataView::getUnitPoisonRound,
+ &BattleMsgDataView::getUnitPoisonRoundById);
+
+ view["getUnitFrostbiteRound"] = sol::overload<>(
+ &BattleMsgDataView::getUnitFrostbiteRound,
+ &BattleMsgDataView::getUnitFrostbiteRoundById);
+
+ view["getUnitBlisterRound"] = sol::overload<>(&BattleMsgDataView::getUnitBlisterRound,
+ &BattleMsgDataView::getUnitBlisterRoundById);
+
+ view["getUnitTransformRound"] = sol::overload<>(
+ &BattleMsgDataView::getUnitTransformRound,
+ &BattleMsgDataView::getUnitTransformRoundById);
+ }
+
private:
std::optional getPlayer(const game::CMidgardID& playerId) const;
diff --git a/mss32/include/bindings/battlemsgdataviewmutable.h b/mss32/include/bindings/battlemsgdataviewmutable.h
new file mode 100644
index 00000000..906c4ae4
--- /dev/null
+++ b/mss32/include/bindings/battlemsgdataviewmutable.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2023 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef BATTLEMSGDATAVIEWMUTABLE_H
+#define BATTLEMSGDATAVIEWMUTABLE_H
+
+#include "battlemsgdataview.h"
+
+namespace bindings {
+
+class BattleMsgDataViewMutable : public BattleMsgDataView
+{
+public:
+ BattleMsgDataViewMutable(game::BattleMsgData* battleMsgData,
+ const game::IMidgardObjectMap* objectMap);
+
+ static void bind(sol::state& lua);
+
+ void setRetreatStatus(bool attacker, std::uint8_t value);
+ void setDecidedToRetreat();
+
+private:
+ game::BattleMsgData* battleMsgData;
+ const game::IMidgardObjectMap* objectMap;
+};
+
+} // namespace bindings
+
+#endif // BATTLEMSGDATAVIEWMUTABLE_H
diff --git a/mss32/include/bindings/buildingview.h b/mss32/include/bindings/buildingview.h
new file mode 100644
index 00000000..ebe5301f
--- /dev/null
+++ b/mss32/include/bindings/buildingview.h
@@ -0,0 +1,57 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef BUILDINGVIEW_H
+#define BUILDINGVIEW_H
+
+#include "currencyview.h"
+#include "idview.h"
+#include
+
+namespace sol {
+class state;
+}
+
+namespace game {
+struct TBuildingType;
+}
+
+namespace bindings {
+
+class BuildingView
+{
+public:
+ BuildingView(const game::TBuildingType* building);
+
+ static void bind(sol::state& lua);
+
+ IdView getId() const;
+ CurrencyView getCost() const;
+ int getCategory() const;
+ std::optional getRequiredBuilding() const;
+ int getUnitBranch() const;
+ int getLevel() const;
+
+private:
+ const game::TBuildingType* building;
+};
+
+} // namespace bindings
+
+#endif // BUILDINGVIEW_H
diff --git a/mss32/include/bindings/crystalview.h b/mss32/include/bindings/crystalview.h
new file mode 100644
index 00000000..93b60d1d
--- /dev/null
+++ b/mss32/include/bindings/crystalview.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef CRYSTALVIEW_H
+#define CRYSTALVIEW_H
+
+#include "idview.h"
+#include "point.h"
+
+namespace sol {
+class state;
+}
+
+namespace game {
+struct CMidCrystal;
+}
+
+namespace bindings {
+
+class CrystalView
+{
+public:
+ CrystalView(const game::CMidCrystal* crystal);
+
+ static void bind(sol::state& lua);
+
+ IdView getId() const;
+ int getResourceType() const;
+ Point getPosition() const;
+
+private:
+ const game::CMidCrystal* crystal;
+};
+
+} // namespace bindings
+
+#endif // CRYSTALVIEW_H
diff --git a/mss32/include/bindings/fortview.h b/mss32/include/bindings/fortview.h
index 0f39e947..67a34ecc 100644
--- a/mss32/include/bindings/fortview.h
+++ b/mss32/include/bindings/fortview.h
@@ -50,6 +50,7 @@ class FortView
IdView getId() const;
Point getPosition() const;
+ Point getEntrance() const;
std::optional getOwner() const;
GroupView getGroup() const;
std::optional getVisitor() const;
diff --git a/mss32/include/bindings/gameview.h b/mss32/include/bindings/gameview.h
new file mode 100644
index 00000000..653e0eb7
--- /dev/null
+++ b/mss32/include/bindings/gameview.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2023 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef GAMEVIEW_H
+#define GAMEVIEW_H
+
+namespace sol {
+class state;
+}
+
+namespace bindings {
+
+class GameView
+{
+public:
+ static void bind(sol::state& lua);
+
+ int getUnitMaxDamage() const;
+ int getUnitMinDamage() const;
+ int getLeaderAdditionalDamage() const;
+ int getUnitMaxArmor() const;
+ bool isEditor() const;
+};
+
+} // namespace bindings
+
+#endif // GAMEVIEW_H
diff --git a/mss32/include/bindings/globalvariablesview.h b/mss32/include/bindings/globalvariablesview.h
new file mode 100644
index 00000000..d331dd90
--- /dev/null
+++ b/mss32/include/bindings/globalvariablesview.h
@@ -0,0 +1,133 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef GLOBALVARIABLESVIEW_H
+#define GLOBALVARIABLESVIEW_H
+
+#include "currencyview.h"
+
+namespace sol {
+class state;
+}
+
+namespace game {
+struct GlobalVariables;
+}
+
+namespace bindings {
+
+class GlobalVariablesView
+{
+public:
+ GlobalVariablesView(const game::GlobalVariables* variables);
+
+ static void bind(sol::state& lua);
+
+ int getMorale(int tier) const;
+ int getWeapnMstr() const;
+
+ int getBatInit() const;
+ int getBatDamage() const;
+ int getBatRound() const;
+ int getBatBreak() const;
+ int getBatBModif() const;
+
+ int getBatBoostd(int level) const;
+ int getBatLowerd(int level) const;
+ int getBatLoweri() const;
+
+ int getLdrMaxAbil() const;
+ int getSpyDiscov() const;
+
+ int getPoisonS() const;
+ int getPoisonC() const;
+
+ int getBribe() const;
+
+ int getStealRace() const;
+ int getStealNeut() const;
+
+ int getRiotMin() const;
+ int getRiotMax() const;
+ int getRiotDmg() const;
+
+ int getSellRatio() const;
+
+ int getTCapture() const;
+ int getTCapital() const;
+ int getTCity(int tier) const;
+
+ CurrencyView getRodCost() const;
+ int getRodRange() const;
+
+ int getCrystalP() const;
+
+ int getConstUrg() const;
+
+ int getRegenLWar() const;
+ int getRegenRuin() const;
+
+ int getDPeace() const;
+ int getDWar() const;
+ int getDNeutral() const;
+ int getDGold() const;
+ int getDMkAlly() const;
+ int getDAttackSc() const;
+ int getDAttackFo() const;
+ int getDAttackPc() const;
+ int getDRod() const;
+ int getDRefAlly() const;
+ int getDBkAlly() const;
+ int getDNoble() const;
+ int getDBkaChnc() const;
+ int getDBkaTurn() const;
+
+ int getProt(int tier) const;
+ int getProtCap() const;
+
+ int getBonusE() const;
+ int getBonusA() const;
+ int getBonusH() const;
+ int getBonusV() const;
+
+ int getIncomeE() const;
+ int getIncomeA() const;
+ int getIncomeH() const;
+ int getIncomeV() const;
+
+ int getGuRange() const;
+ int getPaRange() const;
+ int getLoRange() const;
+
+ int getDfendBonus() const;
+ int getTalisChrg() const;
+
+ int getSplpwr(int level) const;
+
+ int getGainSpell() const;
+
+ int getDefendBonus() const;
+
+private:
+ const game::GlobalVariables* variables;
+};
+
+} // namespace bindings
+
+#endif // GLOBALVARIABLESVIEW_H
diff --git a/mss32/include/bindings/globalview.h b/mss32/include/bindings/globalview.h
new file mode 100644
index 00000000..ead91a1f
--- /dev/null
+++ b/mss32/include/bindings/globalview.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef GLOBALVIEW_H
+#define GLOBALVIEW_H
+
+#include "globalvariablesview.h"
+
+namespace sol {
+class state;
+}
+
+namespace bindings {
+
+class GlobalView
+{
+public:
+ static void bind(sol::state& lua);
+
+ GlobalVariablesView getGlobalVariables() const;
+};
+
+} // namespace bindings
+
+#endif // GLOBALVIEW_H
diff --git a/mss32/include/bindings/groupview.h b/mss32/include/bindings/groupview.h
index e8d25f2f..47635643 100644
--- a/mss32/include/bindings/groupview.h
+++ b/mss32/include/bindings/groupview.h
@@ -58,6 +58,11 @@ class GroupView
bool hasUnit(const bindings::UnitView& unit) const;
bool hasUnitById(const bindings::IdView& unitId) const;
+ int getUnitPosition(const bindings::UnitView& unit) const;
+ int getUnitPositionById(const bindings::IdView& unitId) const;
+
+ int getSubrace() const;
+
protected:
const game::CMidUnitGroup* group;
const game::IMidgardObjectMap* objectMap;
diff --git a/mss32/include/bindings/idview.h b/mss32/include/bindings/idview.h
index 61b3c2fc..9414a9fe 100644
--- a/mss32/include/bindings/idview.h
+++ b/mss32/include/bindings/idview.h
@@ -56,9 +56,11 @@ struct IdView
// For Lua hash table
int getValue() const;
+ int getType() const;
int getTypeIndex() const;
static IdView getEmptyId();
+ static IdView getSummonId(int position);
game::CMidgardID id;
};
diff --git a/mss32/include/bindings/locationview.h b/mss32/include/bindings/locationview.h
index e2c68654..b18de57f 100644
--- a/mss32/include/bindings/locationview.h
+++ b/mss32/include/bindings/locationview.h
@@ -22,6 +22,7 @@
#include "idview.h"
#include "point.h"
+#include
namespace sol {
class state;
@@ -43,6 +44,7 @@ class LocationView
IdView getId() const;
Point getPosition() const;
int getRadius() const;
+ std::string getName() const;
private:
const game::CMidLocation* location;
diff --git a/mss32/include/bindings/merchantview.h b/mss32/include/bindings/merchantview.h
new file mode 100644
index 00000000..ac31cddf
--- /dev/null
+++ b/mss32/include/bindings/merchantview.h
@@ -0,0 +1,60 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MERCHANTVIEW_H
+#define MERCHANTVIEW_H
+
+#include "itembaseview.h"
+#include "siteview.h"
+
+namespace game {
+struct CMidSiteMerchant;
+}
+
+namespace bindings {
+
+class MerchantItemView
+{
+public:
+ MerchantItemView(const game::CMidgardID& globalItemId, int amount);
+
+ static void bind(sol::state& lua);
+
+ ItemBaseView getItemBase() const;
+ int getAmount();
+
+private:
+ game::CMidgardID globalItemId;
+ int amount;
+};
+
+class MerchantView : public SiteView
+{
+public:
+ MerchantView(const game::CMidSiteMerchant* merchant, const game::IMidgardObjectMap* objectMap);
+
+ static void bind(sol::state& lua);
+
+ std::vector getItems() const;
+ bool isMission() const;
+};
+
+} // namespace bindings
+
+#endif // MERCHANTVIEW_H
diff --git a/mss32/include/bindings/mercsview.h b/mss32/include/bindings/mercsview.h
new file mode 100644
index 00000000..510d0eca
--- /dev/null
+++ b/mss32/include/bindings/mercsview.h
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MERCSVIEW_H
+#define MERCSVIEW_H
+
+#include "siteview.h"
+#include "unitimplview.h"
+
+namespace game {
+struct CMidSiteMercs;
+}
+
+namespace bindings {
+
+class MercenaryUnitView
+{
+public:
+ MercenaryUnitView(const game::CMidgardID& unitImplId, bool unique);
+
+ static void bind(sol::state& lua);
+
+ UnitImplView getImpl() const;
+ bool isUnique() const;
+
+private:
+ game::CMidgardID unitImplId;
+ bool unique;
+};
+
+class MercsView : public SiteView
+{
+public:
+ MercsView(const game::CMidSiteMercs* mercs, const game::IMidgardObjectMap* objectMap);
+
+ static void bind(sol::state& lua);
+
+ std::vector getUnits() const;
+};
+
+} // namespace bindings
+
+#endif // MERCSVIEW_H
diff --git a/mss32/include/bindings/playerview.h b/mss32/include/bindings/playerview.h
index d83e07e7..a8083498 100644
--- a/mss32/include/bindings/playerview.h
+++ b/mss32/include/bindings/playerview.h
@@ -20,6 +20,7 @@
#ifndef PLAYERVIEW_H
#define PLAYERVIEW_H
+#include "buildingview.h"
#include "currencyview.h"
#include "idview.h"
#include
@@ -53,6 +54,10 @@ class PlayerView
std::optional getFog() const;
+ std::vector getBuildings() const;
+ bool hasBuilding(const std::string& id) const;
+ bool hasBuildingById(const IdView& id) const;
+
private:
const game::CMidPlayer* player;
const game::IMidgardObjectMap* objectMap;
diff --git a/mss32/include/bindings/resourcemarketview.h b/mss32/include/bindings/resourcemarketview.h
new file mode 100644
index 00000000..6e280601
--- /dev/null
+++ b/mss32/include/bindings/resourcemarketview.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef RESOURCEMARKETVIEW_H
+#define RESOURCEMARKETVIEW_H
+
+#include "currencyview.h"
+#include "siteview.h"
+
+namespace hooks {
+struct CMidSiteResourceMarket;
+}
+
+namespace bindings {
+
+class ResourceMarketView : public SiteView
+{
+public:
+ ResourceMarketView(const hooks::CMidSiteResourceMarket* market,
+ const game::IMidgardObjectMap* objectMap);
+
+ static void bind(sol::state& lua);
+
+ bool isResourceInfinite(game::CurrencyType type) const;
+ CurrencyView getStock() const;
+ bool hasCustomExchangeRates() const;
+
+private:
+ const hooks::CMidSiteResourceMarket* getMarket() const;
+};
+} // namespace bindings
+
+#endif // RESOURCEMARKETVIEW_H
diff --git a/mss32/include/bindings/scenarioview.h b/mss32/include/bindings/scenarioview.h
index 419dda66..ba73d326 100644
--- a/mss32/include/bindings/scenarioview.h
+++ b/mss32/include/bindings/scenarioview.h
@@ -20,6 +20,7 @@
#ifndef SCENARIOVIEW_H
#define SCENARIOVIEW_H
+#include
#include
#include
@@ -48,6 +49,12 @@ class UnitView;
class PlayerView;
class RodView;
class DiplomacyView;
+class ItemView;
+class CrystalView;
+class MerchantView;
+class MercsView;
+class TrainerView;
+class ResourceMarketView;
/**
* Returns stub values if objectMap is null.
@@ -119,6 +126,56 @@ class ScenarioView
/** Searches for unit by id. */
std::optional getUnitById(const IdView& id) const;
+ /** Searches for item by id string. */
+ std::optional getItem(const std::string& id) const;
+ /** Searches for item by id. */
+ std::optional getItemById(const IdView& id) const;
+
+ /** Searches for crystal by id string. */
+ std::optional getCrystal(const std::string& id) const;
+ /** Searches for crystal by id. */
+ std::optional getCrystalById(const IdView& id) const;
+ /** Searches for crystal by coordinate pair. */
+ std::optional getCrystalByCoordinates(int x, int y) const;
+ /** Searches for crystal at specified point. */
+ std::optional getCrystalByPoint(const Point& p) const;
+
+ /** Searches for merchant by id string. */
+ std::optional getMerchant(const std::string& id) const;
+ /** Searches for merchant by id. */
+ std::optional getMerchantById(const IdView& id) const;
+ /** Searches for merchant by coordinate pair. */
+ std::optional getMerchantByCoordinates(int x, int y) const;
+ /** Searches for merchant at specified point. */
+ std::optional getMerchantByPoint(const Point& p) const;
+
+ /** Searches for mercenaries by id string. */
+ std::optional getMercs(const std::string& id) const;
+ /** Searches for mercenaries by id. */
+ std::optional getMercsById(const IdView& id) const;
+ /** Searches for mercenaries by coordinate pair. */
+ std::optional getMercsByCoordinates(int x, int y) const;
+ /** Searches for mercenaries at specified point. */
+ std::optional getMercsByPoint(const Point& p) const;
+
+ /** Searches for trainer by id string. */
+ std::optional getTrainer(const std::string& id) const;
+ /** Searches for trainer by id. */
+ std::optional getTrainerById(const IdView& id) const;
+ /** Searches for trainer by coordinate pair. */
+ std::optional getTrainerByCoordinates(int x, int y) const;
+ /** Searches for trainer at specified point. */
+ std::optional getTrainerByPoint(const Point& p) const;
+
+ /** Searches for market by id string. */
+ std::optional getMarket(const std::string& id) const;
+ /** Searches for market by id. */
+ std::optional getMarketById(const IdView& id) const;
+ /** Searches for market by coordinate pair. */
+ std::optional getMarketByCoordinates(int x, int y) const;
+ /** Searches for market at specified point. */
+ std::optional getMarketByPoint(const Point& p) const;
+
/** Searches for stack that has specified unit among all the stacks in the whole scenario. */
std::optional findStackByUnit(const UnitView& unit) const;
std::optional findStackByUnitId(const IdView& unitId) const;
@@ -137,11 +194,29 @@ class ScenarioView
std::optional findRuinByUnitId(const IdView& unitId) const;
std::optional findRuinByUnitIdString(const std::string& unitId) const;
+ std::string getName() const;
+ std::string getDescription() const;
+ std::string getAuthor() const;
+ std::uint32_t getSeed() const;
int getCurrentDay() const;
int getSize() const;
+ int getDifficulty() const;
std::optional getDiplomacy() const;
+ void forEachStack(const std::function& callback) const;
+ void forEachLocation(const std::function& callback) const;
+ void forEachFort(const std::function& callback) const;
+ void forEachRuin(const std::function& callback) const;
+ void forEachRod(const std::function& callback) const;
+ void forEachPlayer(const std::function& callback) const;
+ void forEachUnit(const std::function& callback) const;
+ void forEachCrystal(const std::function& callback) const;
+ void forEachMerchant(const std::function& callback) const;
+ void forEachMercenary(const std::function& callback) const;
+ void forEachTrainer(const std::function& callback) const;
+ void forEachMarket(const std::function& callback) const;
+
private:
const game::CMidgardID* getObjectId(int x, int y, game::IdType type) const;
diff --git a/mss32/include/bindings/siteview.h b/mss32/include/bindings/siteview.h
new file mode 100644
index 00000000..e7f92939
--- /dev/null
+++ b/mss32/include/bindings/siteview.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef SITEVIEW_H
+#define SITEVIEW_H
+
+#include "idview.h"
+#include "playerview.h"
+#include "point.h"
+#include
+
+namespace sol {
+class state;
+}
+
+namespace game {
+struct CMidSite;
+struct IMidgardObjectMap;
+} // namespace game
+
+namespace bindings {
+
+class SiteView
+{
+public:
+ SiteView(const game::CMidSite* site, const game::IMidgardObjectMap* objectMap);
+
+ static void bind(sol::state& lua);
+
+ IdView getId() const;
+ Point getPosition() const;
+ std::vector getVisitors() const;
+
+protected:
+ template
+ static void bindAccessMethods(T& view)
+ {
+ view["id"] = sol::property(&SiteView::getId);
+ view["position"] = sol::property(&SiteView::getPosition);
+ view["visitors"] = sol::property(&SiteView::getVisitors);
+ }
+
+ const game::CMidSite* site;
+ const game::IMidgardObjectMap* objectMap;
+};
+
+} // namespace bindings
+
+#endif // SITEVIEW_H
diff --git a/mss32/include/bindings/stackview.h b/mss32/include/bindings/stackview.h
index 4f208add..597447d4 100644
--- a/mss32/include/bindings/stackview.h
+++ b/mss32/include/bindings/stackview.h
@@ -69,6 +69,10 @@ class StackView
std::vector getInventoryItems() const;
std::optional getLeaderEquippedItem(const game::EquippedItemIdx& idx) const;
+ int getOrder() const;
+ IdView getOrderTargetId() const;
+ int getAiOrder() const;
+
private:
const game::CMidStack* stack;
const game::IMidgardObjectMap* objectMap;
diff --git a/mss32/include/bindings/trainerview.h b/mss32/include/bindings/trainerview.h
new file mode 100644
index 00000000..bd942bab
--- /dev/null
+++ b/mss32/include/bindings/trainerview.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TRAINERVIEW_H
+#define TRAINERVIEW_H
+
+#include "siteview.h"
+
+namespace game {
+struct CMidSiteTrainer;
+}
+
+namespace bindings {
+
+class TrainerView : public SiteView
+{
+public:
+ TrainerView(const game::CMidSiteTrainer* trainer, const game::IMidgardObjectMap* objectMap);
+
+ static void bind(sol::state& lua);
+};
+
+} // namespace bindings
+
+#endif // TRAINERVIEW_H
diff --git a/mss32/include/bindings/unitimplview.h b/mss32/include/bindings/unitimplview.h
index 55773e8c..1b019e99 100644
--- a/mss32/include/bindings/unitimplview.h
+++ b/mss32/include/bindings/unitimplview.h
@@ -99,8 +99,10 @@ class UnitImplView
std::optional getAttack() const;
/** Returns secondary attack. */
std::optional getAttack2() const;
- /** Returns alternative attack. */
+ /** Returns alternative attack for primary attack. */
std::optional getAltAttack() const;
+ /** Returns alternative attack for secondary attack. */
+ std::optional getAltAttack2() const;
/** Returns immune category id for specified attack class id. */
int getImmuneToAttackClass(int attackClassId) const;
diff --git a/mss32/include/categories.h b/mss32/include/categories.h
index b6c6f3de..85ba7236 100644
--- a/mss32/include/categories.h
+++ b/mss32/include/categories.h
@@ -52,12 +52,6 @@ struct Category
T id;
};
-struct LAttitudesCategoryTable : CEnumConstantTable
-{ };
-
-struct LAttitudesCategory : public Category
-{ };
-
static const int emptyCategoryId = -1;
namespace CategoryTableApi {
@@ -99,7 +93,7 @@ struct Api
/** Looks for category with the specified id, otherwise initializes the value with null table
* and id = -1. */
- using FindCategoryById = Category*(__thiscall*)(Table* thisptr,
+ using FindCategoryById = Category*(__thiscall*)(const Table* thisptr,
Category* value,
const int* categoryId);
FindCategoryById findCategoryById;
diff --git a/mss32/include/categoryids.h b/mss32/include/categoryids.h
index 5e419937..ab466746 100644
--- a/mss32/include/categoryids.h
+++ b/mss32/include/categoryids.h
@@ -404,6 +404,36 @@ enum class AiSpellId : int
Dwarf,
};
+/** Resource ids from Lres.dbf. */
+enum class ResourceId : int
+{
+ Gold,
+ InfernalMana, /**< L_RED */
+ LifeMana, /**< L_YELLOW */
+ DeathMana, /**< L_ORANGE */
+ RunicMana, /**< L_WHITE */
+ GroveMana, /**< L_BLUE */
+};
+
+/** Noble action ids from LAction.dbf. */
+enum class ActionId : int
+{
+ PoisonStack,
+ Spy,
+ StealItem,
+ Assassinate,
+ Misfit,
+ Duel,
+ PoisonCity,
+ StealSpell,
+ Bribe,
+ StealGold,
+ StealMerchant,
+ StealMage,
+ SpyRuin,
+ RiotCity,
+};
+
} // namespace game
#endif // CATEGORYIDS_H
diff --git a/mss32/include/cmdstackvisitmsg.h b/mss32/include/cmdstackvisitmsg.h
new file mode 100644
index 00000000..a83fc4c2
--- /dev/null
+++ b/mss32/include/cmdstackvisitmsg.h
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef CMDSTACKVISITMSG_H
+#define CMDSTACKVISITMSG_H
+
+#include "commandmsg.h"
+
+namespace game {
+
+struct CCmdStackVisitMsg : public CCommandMsg
+{
+ CMidgardID visitorStackId;
+ CMidgardID siteId;
+};
+
+assert_size(CCmdStackVisitMsg, 24);
+
+}
+
+#endif // CMDSTACKVISITMSG_H
diff --git a/mss32/include/currency.h b/mss32/include/currency.h
index 18e718d3..ad4fe119 100644
--- a/mss32/include/currency.h
+++ b/mss32/include/currency.h
@@ -165,6 +165,10 @@ struct Api
std::int16_t value);
SetCurrency set;
+ /** Returns specified bank currency. */
+ using GetCurrency = std::int16_t(__thiscall*)(const Bank* bank, CurrencyType currencyType);
+ GetCurrency get;
+
/** Returns true if all currencies in bank are zero. */
using IsZero = bool(__thiscall*)(const Bank* bank);
IsZero isZero;
diff --git a/mss32/include/customaibattle.h b/mss32/include/customaibattle.h
new file mode 100644
index 00000000..10170f95
--- /dev/null
+++ b/mss32/include/customaibattle.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef CUSTOMAIBATTLE_H
+#define CUSTOMAIBATTLE_H
+
+#include "categoryids.h"
+#include
+#include
+
+namespace hooks {
+
+using AttitudeBattleLogicMap = std::unordered_map;
+
+struct CustomAiBattleLogic
+{
+ AttitudeBattleLogicMap attitudeBattleLogic;
+ bool customBattleLogicEnabled;
+};
+
+const CustomAiBattleLogic& getCustomAiBattleLogic();
+
+void initializeCustomAiBattleLogic();
+
+} // namespace hooks
+
+#endif // CUSTOMAIBATTLE_H
diff --git a/mss32/include/customnobleactioncategories.h b/mss32/include/customnobleactioncategories.h
new file mode 100644
index 00000000..f4872d12
--- /dev/null
+++ b/mss32/include/customnobleactioncategories.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef CUSTOMNOBLEACTIONCATEGORIES_H
+#define CUSTOMNOBLEACTIONCATEGORIES_H
+
+#include "nobleactioncat.h"
+#include
+
+namespace hooks {
+
+using CustomNobleActionCat = std::pair;
+
+struct CustomNobleActionCategories
+{
+ CustomNobleActionCat stealMarket;
+};
+
+CustomNobleActionCategories& getCustomNobleActionCategories();
+
+game::LNobleActionCatTable* __fastcall nobleActionCatTableCtorHooked(
+ game::LNobleActionCatTable* thisptr,
+ int /*%edx*/,
+ const char* globalsFolderPath,
+ void* codeBaseEnvProxy);
+
+} // namespace hooks
+
+#endif // CUSTOMNOBLEACTIONCATEGORIES_H
diff --git a/mss32/include/customnobleactionhooks.h b/mss32/include/customnobleactionhooks.h
new file mode 100644
index 00000000..7737deec
--- /dev/null
+++ b/mss32/include/customnobleactionhooks.h
@@ -0,0 +1,63 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef CUSTOMNOBLEACTIONHOOKS_H
+#define CUSTOMNOBLEACTIONHOOKS_H
+
+#include "d2set.h"
+#include "nobleactioncat.h"
+
+namespace game {
+struct INobleActionResult;
+struct IMidgardObjectMap;
+struct CMidgardID;
+struct String;
+struct CCmdNobleResultMsg;
+struct CPhaseGame;
+struct CMidPlayer;
+} // namespace game
+
+namespace hooks {
+
+game::INobleActionResult* __stdcall createNobleActionResultHooked(
+ game::IMidgardObjectMap* objectMap,
+ const game::LNobleActionCat* actionCategory,
+ const game::CMidgardID* targetObjectId,
+ const game::CMidgardID* id);
+
+bool __stdcall getSiteNobleActionsHooked(const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* objectId,
+ game::Set* nobleActions);
+
+bool __stdcall getPossibleNobleActionsHooked(const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* objectId,
+ game::Set* nobleActions);
+
+game::String* __stdcall getNobleActionResultDescriptionHooked(
+ game::String* description,
+ const game::LNobleActionCat nobleActionCat,
+ const game::CCmdNobleResultMsg* nobleResultMsg,
+ const game::CPhaseGame* phaseGame,
+ const game::CMidPlayer* player);
+
+} // namespace hooks
+
+#endif // CUSTOMNOBLEACTIONHOOKS_H
diff --git a/mss32/include/ddfoldedinv.h b/mss32/include/ddfoldedinv.h
new file mode 100644
index 00000000..eaf36869
--- /dev/null
+++ b/mss32/include/ddfoldedinv.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef DDFOLDEDINV_H
+#define DDFOLDEDINV_H
+
+#include "middropsource.h"
+#include "middroptarget.h"
+#include "midgardobjectmap.h"
+
+namespace game {
+
+struct CListBoxInterf;
+struct IMidDropManager;
+struct CInventoryCounter;
+struct CursorHandle;
+
+struct CDDFoldedInvData
+{
+ CListBoxInterf* listBox;
+ IMidDropManager* midDropManager;
+ CMidgardID unknownId;
+ CInventoryCounter* inventoryCounter;
+ CMidgardID objectId;
+ char unknown4[4];
+ IMidgardObjectMap* objectMap;
+ char unknown5[28];
+ IMqImage2* dragDropValidImage;
+ IMqImage2* dragDropInvalidImage;
+ SmartPtr defaultCursor;
+ SmartPtr noDragDropCursor;
+ char unknown6[4];
+};
+
+assert_size(CDDFoldedInvData, 84);
+
+// TODO: has bigger vftable than IMidDropSource: 27 methods instead of 10
+struct CDDFoldedInv
+ : public IMidDropSource
+ , public IMidDropTarget
+{
+ CDDFoldedInvData* foldedInvData;
+};
+
+assert_size(CDDFoldedInv, 12);
+
+} // namespace game
+
+#endif // DDFOLDEDINV_H
diff --git a/mss32/include/ddfoldedinvdisplay.h b/mss32/include/ddfoldedinvdisplay.h
new file mode 100644
index 00000000..934e4d30
--- /dev/null
+++ b/mss32/include/ddfoldedinvdisplay.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef DDFOLDEDINVDISPLAY_H
+#define DDFOLDEDINVDISPLAY_H
+
+#include "ddfoldedinv.h"
+#include "miditemext.h"
+
+namespace game {
+
+struct CMidDragDropInterf;
+
+struct CDDFoldedInvDisplayData
+{
+ IMidDropManager* dropManager;
+ bool unknown;
+ char padding[3];
+ int unknown2;
+ int unknown3;
+ CMidDragDropInterf* dragDropInterface;
+ int elementCount;
+ int unknown6;
+ int unknown7;
+};
+
+assert_size(CDDFoldedInvDisplayData, 32);
+
+struct CDDFoldedInvDisplay
+ : public CDDFoldedInv
+ , public IMidItemExt
+{
+ CDDFoldedInvDisplayData* foldedInvDisplayData;
+};
+
+assert_size(CDDFoldedInvDisplay, 20);
+
+} // namespace game
+
+#endif // DDFOLDEDINVDISPLAY_H
diff --git a/mss32/include/ddstackgroup.h b/mss32/include/ddstackgroup.h
index 96d589dd..3f1d6d15 100644
--- a/mss32/include/ddstackgroup.h
+++ b/mss32/include/ddstackgroup.h
@@ -53,6 +53,28 @@ struct CDDStackGroup : public CDDUnitGroup
assert_size(CDDStackGroup, 20);
+namespace CDDStackGroupApi {
+
+struct Api
+{
+ using Constructor = CDDStackGroup*(__thiscall*)(CDDStackGroup* thisptr,
+ CMidUnitGroupAdapter* unitGroupAdapter,
+ const CMidgardID* groupId,
+ IMidgardObjectMap* objectMap,
+ IMidDropManager* dropManager,
+ int leftSide,
+ const CMqRect* rSlotArea,
+ ITask* taskOpenInterf,
+ CPhaseGame* phaseGame,
+ CMidDragDropInterf* dragDropInterf,
+ const char* addUnitDialogName);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CDDStackGroupApi
+
} // namespace game
#endif // DDSTACKGROUP_H
diff --git a/mss32/include/ddstackinventorydisplay.h b/mss32/include/ddstackinventorydisplay.h
new file mode 100644
index 00000000..19348f6e
--- /dev/null
+++ b/mss32/include/ddstackinventorydisplay.h
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef DDSTACKINVENTORYDISPLAY_H
+#define DDSTACKINVENTORYDISPLAY_H
+
+#include "ddfoldedinvdisplay.h"
+
+namespace game {
+
+struct CDDStackInventoryDisplay : public CDDFoldedInvDisplay
+{ };
+
+assert_size(CDDStackInventoryDisplay, 20);
+
+namespace CDDStackInventoryDisplayApi {
+
+struct Api
+{
+ using Constructor = CDDStackInventoryDisplay*(__thiscall*)(CDDStackInventoryDisplay* thisptr,
+ CMidDragDropInterf* dragDrop,
+ CListBoxInterf* listBox,
+ const CMidgardID* objectId,
+ IMidgardObjectMap* objectMap);
+ Constructor constructor;
+
+ using SetElementCount = void(__thiscall*)(CDDStackInventoryDisplay* thisptr, int elementCount);
+ SetElementCount setElementCount;
+};
+
+Api& get();
+
+} // namespace CDDStackInventoryDisplayApi
+
+} // namespace game
+
+#endif // DDSTACKINVENTORYDISPLAY_H
diff --git a/mss32/include/displayhandlers.h b/mss32/include/displayhandlers.h
index 1ec1a99e..34958d62 100644
--- a/mss32/include/displayhandlers.h
+++ b/mss32/include/displayhandlers.h
@@ -20,14 +20,19 @@
#ifndef DISPLAYHANDLERS_H
#define DISPLAYHANDLERS_H
+#include "d2map.h"
#include "idset.h"
#include "imagelayerlist.h"
#include "midgardid.h"
+#include "smartptr.h"
namespace game {
struct IMidgardObjectMap;
struct CMidVillage;
+struct IMapElement;
+struct CMidSite;
+struct alignas(8) TypeDescriptor;
namespace DisplayHandlersApi {
@@ -43,11 +48,70 @@ struct Api
bool animatedIso);
DisplayHandler villageHandler;
+
+ DisplayHandler siteHandler;
};
Api& get();
} // namespace DisplayHandlersApi
+
+struct ImageDisplayHandlerVftable;
+
+struct ImageDisplayHandler
+{
+ ImageDisplayHandlerVftable* vftable;
+ DisplayHandlersApi::Api::DisplayHandler handler;
+};
+
+assert_size(ImageDisplayHandler, 8);
+
+struct ImageDisplayHandlerVftable
+{
+ using Destructor = void(__thiscall*)(ImageDisplayHandler* thisptr, char flags);
+ Destructor destructor;
+
+ using RunHandler = void(__thiscall*)(ImageDisplayHandler* thisptr,
+ ImageLayerList* list,
+ const IMapElement* mapElement,
+ const IMidgardObjectMap* objectMap,
+ const CMidgardID* playerId,
+ const IdSet* objectives,
+ int a6,
+ bool animatedIso);
+ RunHandler runHandler;
+};
+
+assert_vftable_size(ImageDisplayHandlerVftable, 2);
+
+using ImageDisplayHandlerPtr = SmartPtr;
+using ImageDisplayHandlerMap = Map;
+using ImageDisplayHandlerMapIt = MapIterator;
+using ImageDisplayHandlerMapInsertIt = Pair;
+
+using ImageDisplayHandlerMapFind =
+ ImageDisplayHandlerMapIt*(__thiscall*)(ImageDisplayHandlerMap* thisptr,
+ ImageDisplayHandlerMapIt* iterator,
+ const TypeDescriptor** descriptor);
+
+namespace ImageDisplayHandlerApi {
+
+struct Api
+{
+ using AddHandler =
+ ImageDisplayHandlerMapInsertIt*(__thiscall*)(ImageDisplayHandlerMap* thisptr,
+ ImageDisplayHandlerMapInsertIt* iterator,
+ const TypeDescriptor** descriptor,
+ const ImageDisplayHandlerPtr* handler);
+ AddHandler addHandler;
+};
+
+Api& get();
+
+ImageDisplayHandlerMap* instance();
+
+} // namespace ImageDisplayHandlerApi
+
} // namespace game
#endif // DISPLAYHANDLERS_H
diff --git a/mss32/include/displayhandlershooks.h b/mss32/include/displayhandlershooks.h
index 2d533c7f..c900ef2e 100644
--- a/mss32/include/displayhandlershooks.h
+++ b/mss32/include/displayhandlershooks.h
@@ -32,6 +32,14 @@ void __stdcall displayHandlerVillageHooked(game::ImageLayerList* list,
int a6,
bool animatedIso);
+void __stdcall getMapElementIsoLayerImagesHooked(game::ImageLayerList* list,
+ const game::IMapElement* mapElement,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::IdSet* objectives,
+ int unknown,
+ bool animatedIso);
+
}
#endif // DISPLAYHANDLERSHOOKS_H
diff --git a/mss32/include/draganddropinterf.h b/mss32/include/draganddropinterf.h
index e0b32318..87584bfa 100644
--- a/mss32/include/draganddropinterf.h
+++ b/mss32/include/draganddropinterf.h
@@ -63,6 +63,12 @@ struct Api
{
using GetDialog = CDialogInterf*(__thiscall*)(CDragAndDropInterf* thisptr);
GetDialog getDialog;
+
+ using Constructor = CDragAndDropInterf*(__thiscall*)(CDragAndDropInterf* thisptr,
+ const char* dialogName,
+ ITask* task,
+ bool a8);
+ Constructor constructor;
};
Api& get();
diff --git a/mss32/include/dynamiccast.h b/mss32/include/dynamiccast.h
index ad3cba66..542aa833 100644
--- a/mss32/include/dynamiccast.h
+++ b/mss32/include/dynamiccast.h
@@ -55,7 +55,7 @@ struct BaseClassDescriptor
std::uint32_t attributes; /**< Flags, usually 0. */
};
-struct BaseClassArray
+struct alignas(16) BaseClassArray
{
// Nonstandard extension: zero-sized array in struct/union
#pragma warning(suppress : 4200)
@@ -63,7 +63,7 @@ struct BaseClassArray
};
/** Describes inheritance hierarchy of a class. */
-struct ClassHierarchyDescriptor
+struct alignas(16) ClassHierarchyDescriptor
{
std::uint32_t signature;
std::uint32_t attributes;
@@ -77,7 +77,7 @@ struct ClassHierarchyDescriptor
* Pointer to this structure can be found in memory just before class vftable.
* @see http://www.openrce.org/articles/full_view/23 for additional info.
*/
-struct CompleteObjectLocator
+struct alignas(16) CompleteObjectLocator
{
std::uint32_t signature;
std::uint32_t offset; /**< Offset of this vftable in complete class. */
@@ -175,10 +175,24 @@ struct Rtti
TypeDescriptor* IUsNobleType;
TypeDescriptor* IUsSummonType;
TypeDescriptor* IItemExPotionBoostType;
+ TypeDescriptor* IMapElementType;
+ TypeDescriptor* CMidRoadType;
+ TypeDescriptor* CCmdStackVisitMsgType;
+ TypeDescriptor* CMidSiteType;
+
+ BaseClassDescriptor* IMidObjectDescriptor;
+ BaseClassDescriptor* IMidScenarioObjectDescriptor;
+ BaseClassDescriptor* IMapElementDescriptor;
+ BaseClassDescriptor* IAiPriorityDescriptor;
+ BaseClassDescriptor* CMidSiteDescriptor;
+ BaseClassDescriptor* CNetMsgDescriptor;
+ BaseClassDescriptor* CNetMsgMapEntryDescriptor;
};
const Rtti& rtti();
+const void* typeInfoVftable();
+
} // namespace RttiApi
} // namespace game
diff --git a/mss32/include/editor.h b/mss32/include/editor.h
index 9a478210..49022af8 100644
--- a/mss32/include/editor.h
+++ b/mss32/include/editor.h
@@ -20,6 +20,8 @@
#ifndef EDITOR_H
#define EDITOR_H
+#include "intvector.h"
+
namespace game {
struct CMidgardID;
@@ -31,6 +33,12 @@ struct TRaceType;
struct CCapital;
struct CVisitorAddPlayer;
struct String;
+struct CMqPoint;
+struct CMidgardMap;
+struct LSiteCategory;
+struct IMqImage2;
+struct CMidSite;
+struct CTextBoxInterf;
/**
* Returns player id depending on RAD_CASTER radio button selection in DLG_EFFECT_CASTMAP dialog.
@@ -44,7 +52,9 @@ using FindPlayerByRaceCategory = CMidPlayer*(__stdcall*)(const LRaceCategory* ra
IMidgardObjectMap* objectMap);
/** Returns true if tiles are suitable for site or ruin. */
-using CanPlace = bool(__stdcall*)(int, int, int);
+using CanPlace = bool(__stdcall*)(const CMqPoint* position,
+ const CMidgardMap* map,
+ const IMidgardObjectMap* objectMap);
/** Returns number of CMidStack objects on map. */
using CountStacksOnMap = int(__stdcall*)(IMidgardObjectMap* objectMap);
@@ -68,6 +78,26 @@ using GetObjectNamePos = String*(__stdcall*)(String* description,
const IMidgardObjectMap* objectMap,
const CMidgardID* objectId);
+/**
+ * Returns indices (G000SI0000) of site images in .ff files
+ * for specified site category.
+ */
+using GetSiteImageIndices = void(__stdcall*)(IntVector* indices,
+ const LSiteCategory* site,
+ int animatedIso);
+
+using GetSiteImage = IMqImage2*(__stdcall*)(const LSiteCategory* site,
+ int imageIndex,
+ bool animatedIso);
+
+using GetSiteAtPosition = const CMidSite*(__stdcall*)(const CMqPoint* mapPosition,
+ const IMidgardObjectMap* objectMap);
+
+using ShowOrHideSiteOnStrategicMap = void(__stdcall*)(const CMidSite* site,
+ const IMidgardObjectMap* objectMap,
+ const CMidgardID* playerId,
+ int a4);
+
/** Scenario Editor functions that can be hooked. */
struct EditorFunctions
{
@@ -80,6 +110,10 @@ struct EditorFunctions
IsRaceCategoryPlayable isRaceCategoryPlayable;
ChangeCapitalTerrain changeCapitalTerrain;
GetObjectNamePos getObjectNamePos;
+ GetSiteImageIndices getSiteImageIndices;
+ GetSiteImage getSiteImage;
+ GetSiteAtPosition getSiteAtPosition;
+ ShowOrHideSiteOnStrategicMap showOrHideSiteOnStrategicMap;
};
extern EditorFunctions editorFunctions;
diff --git a/mss32/include/effectgiveresources.h b/mss32/include/effectgiveresources.h
new file mode 100644
index 00000000..1d1d1457
--- /dev/null
+++ b/mss32/include/effectgiveresources.h
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef EFFECTGIVERESOURCES_H
+#define EFFECTGIVERESOURCES_H
+
+namespace game {
+struct IEventEffect;
+struct CMidgardID;
+struct Bank;
+} // namespace game
+
+namespace hooks {
+
+game::IEventEffect* createEffectGiveResources(const game::CMidgardID& playerId,
+ const game::Bank& resources,
+ bool add);
+
+}
+
+#endif // EFFECTGIVERESOURCES_H
diff --git a/mss32/include/effectstealmarket.h b/mss32/include/effectstealmarket.h
new file mode 100644
index 00000000..1630f7e7
--- /dev/null
+++ b/mss32/include/effectstealmarket.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef EFFECTSTEALMARKET_H
+#define EFFECTSTEALMARKET_H
+
+#include
+
+namespace game {
+struct IEventEffect;
+struct CMidgardID;
+enum class CurrencyType : int;
+} // namespace game
+
+namespace hooks {
+
+game::IEventEffect* createEffectStealMarket(const game::CMidgardID& marketId,
+ game::CurrencyType resource,
+ std::int16_t amount);
+
+} // namespace hooks
+
+#endif // EFFECTSTEALMARKET_H
diff --git a/mss32/include/encparamidplayer.h b/mss32/include/encparamidplayer.h
index afabdcf4..75993cb7 100644
--- a/mss32/include/encparamidplayer.h
+++ b/mss32/include/encparamidplayer.h
@@ -33,6 +33,24 @@ struct CEncParamIDPlayer : CEncParamBase
assert_size(CEncParamIDPlayer, 16);
+namespace CEncParamIDPlayerApi {
+
+struct Api
+{
+ using Constructor = CEncParamIDPlayer*(__thiscall*)(CEncParamIDPlayer* thisptr,
+ const CMidgardID* objectId,
+ const CMidgardID* playerId,
+ SmartPointer* functor);
+ Constructor constructor;
+
+ using Destructor = void(__thiscall*)(CEncParamIDPlayer* thisptr);
+ Destructor destructor;
+};
+
+Api& get();
+
+} // namespace CEncParamIDPlayerApi
+
} // namespace game
#endif // ENCPARAMPLAYERID_H
diff --git a/mss32/include/eventeffectbase.h b/mss32/include/eventeffectbase.h
new file mode 100644
index 00000000..c3562e7b
--- /dev/null
+++ b/mss32/include/eventeffectbase.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef EVENTEFFECTBASE_H
+#define EVENTEFFECTBASE_H
+
+#include "eventeffect.h"
+
+namespace game {
+
+struct CEventEffectBase : public IEventEffect
+{ };
+
+} // namespace game
+
+#endif // EVENTEFFECTBASE_H
diff --git a/mss32/include/exchangeresourcesmsg.h b/mss32/include/exchangeresourcesmsg.h
new file mode 100644
index 00000000..a06efd61
--- /dev/null
+++ b/mss32/include/exchangeresourcesmsg.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef EXCHANGERESOURCESMSG_H
+#define EXCHANGERESOURCESMSG_H
+
+#include "currency.h"
+#include "midgardid.h"
+#include "netmsg.h"
+#include
+
+namespace game {
+struct TypeDescriptor;
+struct CPhaseGame;
+} // namespace game
+
+namespace hooks {
+
+/** Describes exchange between player and a resource market site. */
+struct CExchangeResourcesMsg : public game::CNetMsg
+{
+ CExchangeResourcesMsg();
+
+ CExchangeResourcesMsg(const game::CMidgardID& siteId,
+ const game::CMidgardID& visitorStackId,
+ game::CurrencyType playerCurrency,
+ game::CurrencyType siteCurrency,
+ std::uint16_t amount);
+
+ game::CMidgardID siteId;
+ game::CMidgardID visitorStackId;
+ game::CurrencyType playerCurrency;
+ game::CurrencyType siteCurrency;
+ std::uint16_t amount;
+};
+
+game::TypeDescriptor* getExchangeResourcesMsgTypeDescriptor();
+
+void sendExchangeResourcesMsg(game::CPhaseGame* phaseGame,
+ const game::CMidgardID& siteId,
+ const game::CMidgardID& visitorStackId,
+ game::CurrencyType playerCurrency,
+ game::CurrencyType marketCurrency,
+ std::uint16_t amount);
+
+} // namespace hooks
+
+#endif // EXCHANGERESOURCESMSG_H
diff --git a/mss32/include/game.h b/mss32/include/game.h
index 1c3b9af6..ceada754 100644
--- a/mss32/include/game.h
+++ b/mss32/include/game.h
@@ -21,10 +21,12 @@
#define GAME_H
#include "attacktypepairvector.h"
+#include "d2set.h"
#include "faceimg.h"
#include "globaldata.h"
#include "idlist.h"
#include "mqpoint.h"
+#include "nobleactioncat.h"
#include "smartptr.h"
namespace game {
@@ -79,6 +81,11 @@ struct LDeathAnimCategory;
struct CMidStreamEnvFile;
struct CMidgardScenarioMap;
struct TBuildingType;
+struct CScenarioVisitor;
+struct LSiteCategory;
+struct CMidSite;
+struct CTextBoxInterf;
+struct CCmdNobleResultMsg;
enum class ModifierElementTypeFlag : int;
@@ -731,6 +738,32 @@ using GetBuildingStatus = BuildingStatus(__stdcall*)(const IMidgardObjectMap* ob
const CMidgardID* buildingId,
bool ignoreBuildTurnAndCost);
+/** Removes stack from plan, fort and object map. */
+using RemoveStack = bool(__stdcall*)(const CMidgardID* stackId,
+ CMidgardPlan* plan,
+ IMidgardObjectMap* objectMap,
+ CScenarioVisitor* visitor);
+
+using GetSiteNameSuffix = const char*(__stdcall*)(const LSiteCategory* siteCategory);
+
+using UpdateEncLayoutSite = void(__stdcall*)(const CMidSite* site, CTextBoxInterf* textBox);
+
+using GetSiteSound = String*(__stdcall*)(String* soundName, const CMidSite* site);
+
+using SiteHasSound = bool(__stdcall*)(const CMidSite* site);
+
+using GetNobleActions = bool(__stdcall*)(const IMidgardObjectMap* objectMap,
+ const CMidgardID* playerId,
+ const CMidgardID* objectId,
+ Set* nobleActions);
+
+using GetNobleActionResultDescription =
+ String*(__stdcall*)(String* description,
+ const LNobleActionCat nobleActionCat,
+ const CCmdNobleResultMsg* nobleResultMsg,
+ const CPhaseGame* phaseGame,
+ const CMidPlayer* player);
+
/** Game and editor functions that can be hooked. */
struct Functions
{
@@ -857,6 +890,16 @@ struct Functions
GetUnitRequiredBuildings getUnitRequiredBuildings;
ComputeMovementCost computeMovementCost;
GetBuildingStatus getBuildingStatus;
+ RemoveStack removeStack;
+ GetSiteNameSuffix getSiteNameSuffix;
+ UpdateEncLayoutSite updateEncLayoutSite;
+ GetSiteSound getSiteSound;
+ SiteHasSound siteHasSound;
+ /** Returns actions that noble can do with the site specified by id. */
+ GetNobleActions getSiteNobleActions;
+ /** Returns all actions that noble can possibly perform on object with specified id. */
+ GetNobleActions getPossibleNobleActions;
+ GetNobleActionResultDescription getNobleActionResultDescription;
};
/** Global variables used in game. */
diff --git a/mss32/include/gameutils.h b/mss32/include/gameutils.h
index 64fd5baa..2438413f 100644
--- a/mss32/include/gameutils.h
+++ b/mss32/include/gameutils.h
@@ -20,6 +20,8 @@
#ifndef GAMEUTILS_H
#define GAMEUTILS_H
+#include "mqpoint.h"
+
namespace game {
struct CMidgardID;
struct CMidUnitGroup;
@@ -42,6 +44,8 @@ struct CMidDiplomacy;
struct CMidgardMapFog;
struct TBuildingType;
struct CPlayerBuildings;
+struct CMidLocation;
+struct CMidStackDestroyed;
} // namespace game
namespace hooks {
@@ -76,6 +80,9 @@ const game::CScenarioInfo* getScenarioInfo(const game::IMidgardObjectMap* object
const game::CMidPlayer* getPlayer(const game::IMidgardObjectMap* objectMap,
const game::CMidgardID* playerId);
+game::CMidPlayer* getPlayerToChange(game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId);
+
const game::CMidPlayer* getPlayer(const game::IMidgardObjectMap* objectMap,
const game::BattleMsgData* battleMsgData,
const game::CMidgardID* unitId);
@@ -86,9 +93,16 @@ const game::CMidPlayer* getPlayerByUnitId(const game::IMidgardObjectMap* objectM
const game::CMidgardID getPlayerIdByUnitId(const game::IMidgardObjectMap* objectMap,
const game::CMidgardID* unitId);
+const game::CMidPlayer* getNeutralPlayer(const game::IMidgardObjectMap* objectMap);
+
+/** Returns player that controls specified unit group. In case of ruins, returns Neutrals player. */
+const game::CMidPlayer* getGroupOwner(const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* groupId);
+
const game::CMidScenVariables* getScenarioVariables(const game::IMidgardObjectMap* objectMap);
const game::CMidgardPlan* getMidgardPlan(const game::IMidgardObjectMap* objectMap);
+game::CMidgardPlan* getMidgardPlanToChange(game::IMidgardObjectMap* objectMap);
const game::CMidgardMap* getMidgardMap(const game::IMidgardObjectMap* objectMap);
@@ -174,6 +188,17 @@ const game::CMidDiplomacy* getDiplomacy(const game::IMidgardObjectMap* objectMap
const game::CMidgardMapFog* getFog(const game::IMidgardObjectMap* objectMap,
const game::CMidPlayer* player);
+const game::CMidLocation* getLocation(const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* locationId);
+
+const game::CMidStackDestroyed* getStackDestroyed(const game::IMidgardObjectMap* objectMap);
+
+bool isInventoryContainsItem(const game::IMidgardObjectMap* objectMap,
+ const game::CMidInventory& inventory,
+ const game::CMidgardID& globalItemId);
+
+const game::CMqPoint getObjectEntrance(const game::CMqPoint& position, int sizeX, int sizeY);
+
} // namespace hooks
#endif // GAMEUTILS_H
diff --git a/mss32/include/globaldata.h b/mss32/include/globaldata.h
index 31ac4253..dfdcbd6a 100644
--- a/mss32/include/globaldata.h
+++ b/mss32/include/globaldata.h
@@ -77,6 +77,7 @@ struct CUnitGenerator;
struct CItemBase;
struct CDynUpgrade;
struct CTileVariation;
+struct CAiAttitudesTable;
using RacesMap = mq_c_s>;
using DynUpgradeList = List>;
@@ -144,9 +145,9 @@ struct GlobalData
int* transf;
DynUpgradeList* dynUpgrade;
CTileVariation* tileVariation;
- int* aiAttitudes;
+ CAiAttitudesTable* aiAttitudes;
int* aiMessages;
- GlobalVariables** globalVariables;
+ GlobalVariables* globalVariables;
CUnitGenerator* unitGenerator;
int initialized;
};
diff --git a/mss32/include/globalvariables.h b/mss32/include/globalvariables.h
index 26d9858c..d1344d74 100644
--- a/mss32/include/globalvariables.h
+++ b/mss32/include/globalvariables.h
@@ -26,8 +26,10 @@
namespace game {
+struct CProxyCodeBaseEnv;
+
/** Holds global game settings read from GVars.dbf. */
-struct GlobalVariables
+struct GlobalVariablesData
{
/** 'MORALE_n' */
int morale[6];
@@ -51,7 +53,7 @@ struct GlobalVariables
int batBmodif;
/** Max abilities leader can learn. 'LDRMAXABIL' */
int maxLeaderAbilities;
- /** Spy discovery change per turn. 'SPY_DISCOV' */
+ /** Spy discovery chance per turn. 'SPY_DISCOV' */
int spyDiscoveryChance;
/** 'POISON_S' */
int poisonStackDamage;
@@ -83,9 +85,9 @@ struct GlobalVariables
int rodRange;
/** Profit per mana crystal or gold mine per turn. 'CRYSTAL_P' */
int crystalProfit;
- /** 'CONST_UPG' */
- int constUpg;
- /** Change to get spells with capture of a capital. 'GAIN_SPELL' */
+ /** 'CONST_URG' */
+ int constUrg;
+ /** Chance to get spells with capture of a capital. 'GAIN_SPELL' */
int gainSpellChance;
/** Bonus per day regeneration for units in ruins. 'REGEN_RUIN' */
int regenRuin;
@@ -155,9 +157,40 @@ struct GlobalVariables
String tutorialName;
};
-assert_size(GlobalVariables, 352);
-assert_offset(GlobalVariables, rodPlacementCost, 148);
-assert_offset(GlobalVariables, talismanCharges, 296);
+assert_size(GlobalVariablesData, 352);
+assert_offset(GlobalVariablesData, rodPlacementCost, 148);
+assert_offset(GlobalVariablesData, talismanCharges, 296);
+
+struct GlobalVariablesDataHooked : public GlobalVariablesData
+{
+ /** Maximum amount of resource the noble can steal from resource market. 'STEAL_RMKT' */
+ int stealRmkt;
+ /** Minimal resource market riot duration in days. 'RMKT_RIOT_MIN' */
+ int rmktRiotMin;
+ /** Maximal resource market riot duration in days. 'RMKT_RIOT_MAX' */
+ int rmktRiotMax;
+};
+
+struct GlobalVariables
+{
+ GlobalVariablesDataHooked* data;
+};
+
+assert_size(GlobalVariables, 4);
+
+namespace GlobalVariablesApi {
+
+struct Api
+{
+ using Constructor = GlobalVariables*(__thiscall*)(GlobalVariables* thisptr,
+ const char* directory,
+ CProxyCodeBaseEnv* proxy);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace GlobalVariablesApi
} // namespace game
diff --git a/mss32/include/globalvariableshooks.h b/mss32/include/globalvariableshooks.h
new file mode 100644
index 00000000..a750a6fc
--- /dev/null
+++ b/mss32/include/globalvariableshooks.h
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef GLOBALVARIABLESHOOKS_H
+#define GLOBALVARIABLESHOOKS_H
+
+namespace game {
+struct GlobalVariables;
+struct CProxyCodeBaseEnv;
+} // namespace game
+
+namespace hooks {
+
+game::GlobalVariables* __fastcall globalVariablesCtorHooked(game::GlobalVariables* thisptr,
+ int /*%edx*/,
+ const char* directory,
+ game::CProxyCodeBaseEnv* proxy);
+
+}
+
+#endif // GLOBALVARIABLESHOOKS_H
diff --git a/mss32/include/hooks.h b/mss32/include/hooks.h
index b7d70255..7588e0c8 100644
--- a/mss32/include/hooks.h
+++ b/mss32/include/hooks.h
@@ -66,6 +66,11 @@ struct IUsUnit;
struct TBuildingType;
struct TUsUnitImpl;
struct EditBoxData;
+struct CMidgardPlan;
+struct CScenarioVisitor;
+struct LSiteCategory;
+struct CMidSite;
+struct CTextBoxInterf;
enum class BuildingBranchNumber : int;
enum class CanApplyPotionResult : int;
@@ -295,6 +300,23 @@ game::BuildingStatus __stdcall getBuildingStatusHooked(const game::IMidgardObjec
const game::CMidgardID* buildingId,
bool ignoreBuildTurnAndCost);
+bool __stdcall removeStackHooked(const game::CMidgardID* stackId,
+ game::CMidgardPlan* plan,
+ game::IMidgardObjectMap* objectMap,
+ game::CScenarioVisitor* visitor);
+
+bool __stdcall setStackSrcTemplateHooked(const game::CMidgardID* stackId,
+ const game::CMidgardID* stackTemplateId,
+ game::IMidgardObjectMap* objectMap,
+ int apply);
+const char* __stdcall getSiteNameSuffixHooked(const game::LSiteCategory* siteCategory);
+
+void __stdcall updateEncLayoutSiteHooked(const game::CMidSite* site, game::CTextBoxInterf* textBox);
+
+game::String* __stdcall getSiteSoundHooked(game::String* soundName, const game::CMidSite* site);
+
+bool __stdcall siteHasSoundHooked(const game::CMidSite* site);
+
} // namespace hooks
#endif // HOOKS_H
diff --git a/mss32/include/imagelayerlist.h b/mss32/include/imagelayerlist.h
index a3a41965..e1545459 100644
--- a/mss32/include/imagelayerlist.h
+++ b/mss32/include/imagelayerlist.h
@@ -22,6 +22,7 @@
#include "d2list.h"
#include "d2pair.h"
+#include "idset.h"
namespace game {
@@ -30,6 +31,7 @@ struct CIsoLayer;
struct CFortification;
struct IMidgardObjectMap;
struct CMidgardID;
+struct IMapElement;
using ImageLayerPair = Pair;
using ImageLayerList = List;
@@ -56,6 +58,26 @@ struct Api
const IMidgardObjectMap* objectMap,
const CMidgardID* playerId);
AddShieldImageLayer addShieldImageLayer;
+
+ /**
+ * Adds images and iso layers to the list depending on specified map element.
+ * Images describe how map element should look on each layer on a strategic map.
+ * @param list - list of images and corresponding iso layers.
+ * @param mapElement - map element to show on a strategic map.
+ * @param objectMap - objects storage.
+ * @param playerId - id of current player.
+ * @param objectives - list of scenario objective ids.
+ * @param unknown
+ * @param animatedIso - whether strategic map should show animated graphics.
+ */
+ using GetMapElementIsoLayerImages = void(__stdcall*)(ImageLayerList* list,
+ const IMapElement* mapElement,
+ const IMidgardObjectMap* objectMap,
+ const CMidgardID* playerId,
+ const IdSet* objectives,
+ int unknown,
+ bool animatedIso);
+ GetMapElementIsoLayerImages getMapElementIsoLayerImages;
};
Api& get();
diff --git a/mss32/include/interface.h b/mss32/include/interface.h
index 6b3661f5..e89b4372 100644
--- a/mss32/include/interface.h
+++ b/mss32/include/interface.h
@@ -68,10 +68,8 @@ struct CInterfaceVftable
using Destructor = void(__thiscall*)(CInterface* thisptr, char flags);
Destructor destructor;
- /** Draws childs of specified root element. */
- using Draw = void(__thiscall*)(CInterface* thisptr,
- CInterface* rootElement,
- IMqRenderer2* renderer);
+ /** Draws interface element and its children. */
+ using Draw = void(__stdcall*)(CInterface* thisptr, IMqRenderer2* renderer);
Draw draw;
/** Handles mouse position changes. */
diff --git a/mss32/include/isoview.h b/mss32/include/isoview.h
index 234559c3..8b773feb 100644
--- a/mss32/include/isoview.h
+++ b/mss32/include/isoview.h
@@ -64,6 +64,19 @@ struct CIsoView : public CFullScreenInterf
CIsoViewData* isoViewData;
};
+namespace CIsoViewApi {
+
+struct Api
+{
+ using UpdateSelectedTileInfo = void(__thiscall*)(CIsoView* thisptr,
+ const CMqPoint* mapPosition);
+ UpdateSelectedTileInfo updateSelectedTileInfo;
+};
+
+Api& get();
+
+} // namespace CIsoViewApi
+
} // namespace editor
} // namespace game
diff --git a/mss32/include/isoviewhooks.h b/mss32/include/isoviewhooks.h
new file mode 100644
index 00000000..712c9083
--- /dev/null
+++ b/mss32/include/isoviewhooks.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef ISOVIEWHOOKS_H
+#define ISOVIEWHOOKS_H
+
+namespace game {
+
+namespace editor {
+struct CIsoView;
+}
+
+struct CMqPoint;
+} // namespace game
+
+namespace hooks {
+
+void __fastcall updateSelectedTileInfoHooked(game::editor::CIsoView* thisptr,
+ int /* %edx */,
+ const game::CMqPoint* mapPosition);
+
+}
+
+#endif // ISOVIEWHOOKS_H
diff --git a/mss32/include/log.h b/mss32/include/log.h
index 3059902d..83a6a661 100644
--- a/mss32/include/log.h
+++ b/mss32/include/log.h
@@ -20,15 +20,15 @@
#ifndef LOG_H
#define LOG_H
-#include
+#include
namespace hooks {
/** Prints message to the file only if debug mode setting is enabled. */
-void logDebug(const std::string& logFile, const std::string& message);
+void logDebug(std::string_view logFile, std::string_view message);
/** Prints message to the file. */
-void logError(const std::string& logFile, const std::string& message);
+void logError(std::string_view logFile, std::string_view message);
} // namespace hooks
diff --git a/mss32/include/lordtype.h b/mss32/include/lordtype.h
index 04461fa6..1067154b 100644
--- a/mss32/include/lordtype.h
+++ b/mss32/include/lordtype.h
@@ -81,6 +81,7 @@ assert_size(TLordTypeData, 56);
assert_offset(TLordTypeData, name, 4);
assert_offset(TLordTypeData, description, 12);
assert_offset(TLordTypeData, buildList, 32);
+assert_offset(TLordTypeData, lordCategory, 44);
/** Holds information read from GLord.dbf. */
struct TLordType : public IMidObject
diff --git a/mss32/include/mainview2.h b/mss32/include/mainview2.h
index 6544d300..26a81b71 100644
--- a/mss32/include/mainview2.h
+++ b/mss32/include/mainview2.h
@@ -32,6 +32,7 @@ struct CZoomInterface;
struct CDialogInterf;
struct IMqImage2;
struct CToggleButton;
+struct CCommandMsg;
struct CMainView2
: public CFullScreenInterf
@@ -46,7 +47,7 @@ struct CMainView2
CDialogInterf* resourcePopup;
char unknown14;
char padding[3];
- int unknown15;
+ CInterface* emptyInterface;
CDialogInterf* dialogInterf;
CMqRect imgResourceArea;
CMqPoint bgndFillSize;
@@ -58,7 +59,7 @@ struct CMainView2
IMqImage2* isoPalFill;
CMqPoint imgPaletteTopLeftXBottomRightY;
IMqImage2* isoBlackbar;
- int unknown34;
+ int isoAreaWidthBlackbarAdjusted;
int unknown35;
bool unknown36;
bool unknown37;
@@ -97,6 +98,10 @@ struct Api
CMainView2* mainView,
ToggleButtonCallback* callback);
CreateToggleButtonFunctor createToggleButtonFunctor;
+
+ using HandleCmdStackVisitMsg = void(__thiscall*)(CMainView2* thisptr,
+ const CCommandMsg* stackVisitMsg);
+ HandleCmdStackVisitMsg handleCmdStackVisitMsg;
};
Api& get();
diff --git a/mss32/include/mainview2hooks.h b/mss32/include/mainview2hooks.h
index 5d3b6620..dcc5228c 100644
--- a/mss32/include/mainview2hooks.h
+++ b/mss32/include/mainview2hooks.h
@@ -22,12 +22,17 @@
namespace game {
struct CMainView2;
-}
+struct CCommandMsg;
+} // namespace game
namespace hooks {
void __fastcall mainView2ShowIsoDialogHooked(game::CMainView2* thisptr, int /*%edx*/);
-}
+void __fastcall mainView2HandleCmdStackVisitMsgHooked(game::CMainView2* thisptr,
+ int /*%edx*/,
+ const game::CCommandMsg* stackVisitMsg);
+
+} // namespace hooks
#endif // MAINVIEW2HOOKS_H
diff --git a/mss32/include/mapelement.h b/mss32/include/mapelement.h
index 69baa03f..2d36bdaa 100644
--- a/mss32/include/mapelement.h
+++ b/mss32/include/mapelement.h
@@ -43,19 +43,9 @@ struct IMapElementVftable
{
using Destructor = void(__thiscall*)(IMapElement* thisptr, char flags);
Destructor destructor;
-
- using Initialize = bool(__thiscall*)(IMapElement* thisptr,
- const IMidgardObjectMap* objectMap,
- const CMidgardID* leaderId,
- const CMidgardID* ownerId,
- const CMidgardID* subraceId,
- const CMqPoint* position);
- Initialize initialize;
-
- void* method2;
};
-assert_vftable_size(IMapElementVftable, 3);
+assert_vftable_size(IMapElementVftable, 1);
} // namespace game
diff --git a/mss32/include/mapinterf.h b/mss32/include/mapinterf.h
index eda0e5ff..2eed223d 100644
--- a/mss32/include/mapinterf.h
+++ b/mss32/include/mapinterf.h
@@ -22,7 +22,10 @@
#include "isoview.h"
-namespace game::editor {
+namespace game {
+struct CToggleButton;
+
+namespace editor {
struct CTaskMapChange;
@@ -76,12 +79,27 @@ struct Api
CreateTask createAddMountainsTask;
CreateTask createAddTreesTask;
CreateTask createChangeHeightTask;
+
+ struct ToggleButtonCallback
+ {
+ using Callback = void(__thiscall*)(CMapInterf* thisptr, bool, CToggleButton*);
+
+ Callback callback;
+ int unknown;
+ };
+
+ using CreateToggleButtonFunctor = SmartPointer*(__stdcall*)(SmartPointer* functor,
+ int a2,
+ CMapInterf* mapInterf,
+ ToggleButtonCallback* callback);
+ CreateToggleButtonFunctor createToggleButtonFunctor;
};
Api& get();
} // namespace CMapInterfApi
-} // namespace game::editor
+} // namespace editor
+} // namespace game
#endif // MAPINTERF_H
diff --git a/mss32/include/menubase.h b/mss32/include/menubase.h
index 50052610..a086243f 100644
--- a/mss32/include/menubase.h
+++ b/mss32/include/menubase.h
@@ -181,6 +181,18 @@ struct Api
CMenuBase* menu,
const PictureCallback* callback);
CreatePictureFunctor createPictureFunctor;
+
+ using RadioButtonCallback = void(__thiscall*)(CMenuBase* thisptr, int index);
+
+ /**
+ * Creates functor for radio button that will handle button press events.
+ * Reused from CMenuLord.
+ */
+ using CreateRadioButtonFunctor = SmartPointer(__stdcall*)(SmartPointer* functor,
+ int,
+ CMenuBase* menu,
+ const RadioButtonCallback* callback);
+ CreateRadioButtonFunctor createRadioButtonFunctor;
};
Api& get();
diff --git a/mss32/include/midclient.h b/mss32/include/midclient.h
index fba61671..1fffc607 100644
--- a/mss32/include/midclient.h
+++ b/mss32/include/midclient.h
@@ -25,25 +25,26 @@
#include "midclientcore.h"
#include "midcommandqueue2.h"
#include "midgardid.h"
+#include "playerlistentry.h"
#include "textmessage.h"
#include "uievent.h"
namespace game {
struct CPhase;
+struct ILoveChat;
+struct INotifyPlayerList;
struct CMidClientData
{
CPhase* phase;
- int unknown2;
- Vector messages;
- List list;
- List list2;
+ bool scenarioStarted;
+ char padding[3];
+ Vector textMessages; /**< Chat, player join and disconnect messages. */
+ List chatList; /**< Chat messages subscribers. */
+ List notifyPlayerList;
List list3;
- int unknown7;
- int unknown8;
- int unknown9;
- int unknown10;
+ Vector playerListEntries;
UiEvent notificationFadeEvent; /**< Restores window notify state. */
/** Flashes game window each second to notify player, for example when battle starts. */
UiEvent notificationShowEvent;
diff --git a/mss32/include/midclientcore.h b/mss32/include/midclientcore.h
index 12b976af..a9295946 100644
--- a/mss32/include/midclientcore.h
+++ b/mss32/include/midclientcore.h
@@ -25,17 +25,18 @@
namespace game {
struct CMidgard;
-struct IMidgardObjectMap;
+struct CMidDataCache2;
struct CMidCommandQueue2;
struct CoreCommandUpdate;
struct CCommandCanIgnore;
struct CMidHotseatManager;
+struct CNetMsg;
struct CMidClientCoreData
{
CMidgard* midgard;
int unknown;
- IMidgardObjectMap* objectMap;
+ CMidDataCache2* dataCache;
int unknown3;
CMidCommandQueue2* commandQueue;
CoreCommandUpdate* coreCommandUpdate;
@@ -54,7 +55,7 @@ namespace CMidClientCoreApi {
struct Api
{
- using GetObjectMap = IMidgardObjectMap*(__thiscall*)(CMidClientCore* thisptr);
+ using GetObjectMap = CMidDataCache2*(__thiscall*)(CMidClientCore* thisptr);
GetObjectMap getObjectMap;
};
diff --git a/mss32/include/midcommandqueue2.h b/mss32/include/midcommandqueue2.h
index 3e890331..d3973b6e 100644
--- a/mss32/include/midcommandqueue2.h
+++ b/mss32/include/midcommandqueue2.h
@@ -82,6 +82,18 @@ struct CMidCommandQueue2::INotifyCQVftable
assert_vftable_size(CMidCommandQueue2::INotifyCQVftable, 2);
+namespace CMidCommandQueue2Api {
+
+struct Api
+{
+ using ProcessCommands = void(__thiscall*)(CMidCommandQueue2* thisptr);
+ ProcessCommands processCommands;
+};
+
+Api& get();
+
+} // namespace CMidCommandQueue2Api
+
} // namespace game
#endif // MIDCOMMANDQUEUE2_H
diff --git a/mss32/include/midcrystal.h b/mss32/include/midcrystal.h
new file mode 100644
index 00000000..fbc4c193
--- /dev/null
+++ b/mss32/include/midcrystal.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDCRYSTAL_H
+#define MIDCRYSTAL_H
+
+#include "aipriority.h"
+#include "mapelement.h"
+#include "midscenarioobject.h"
+#include "resourcetype.h"
+
+namespace game {
+
+/** Represents gold mine or mana crystal in scenario file and game. */
+struct CMidCrystal : public IMidScenarioObject
+{
+ IMapElement mapElement;
+ IAiPriority aiPriority;
+ LResourceType resourceType;
+};
+
+assert_size(CMidCrystal, 48);
+
+} // namespace game
+
+#endif // MIDCRYSTAL_H
diff --git a/mss32/include/middatacache.h b/mss32/include/middatacache.h
index bf9d4478..87b1bbd4 100644
--- a/mss32/include/middatacache.h
+++ b/mss32/include/middatacache.h
@@ -55,6 +55,24 @@ struct CMidDataCache2 : public IMidgardObjectMap
assert_size(CMidDataCache2, 36);
+namespace CMidDataCache2Api {
+
+struct Api
+{
+ using ChangeNotify = void(__thiscall*)(CMidDataCache2* thisptr,
+ CMidDataCache2::INotify* notify);
+
+ /** Adds subscriber that will receive scenario object chages notifications. */
+ ChangeNotify addNotify;
+
+ /** Removes scenario object chages notifications subscriber. */
+ ChangeNotify removeNotify;
+};
+
+Api& get();
+
+} // namespace CMidDataCache2Api
+
} // namespace game
#endif // MIDDATACACHE_H
diff --git a/mss32/include/middragdropinterf.h b/mss32/include/middragdropinterf.h
index 9b5f92f3..5d92bdae 100644
--- a/mss32/include/middragdropinterf.h
+++ b/mss32/include/middragdropinterf.h
@@ -35,6 +35,36 @@ struct CMidDragDropInterf : public CDragAndDropInterf
assert_offset(CMidDragDropInterf, phaseGame, 24);
+namespace CMidDragDropInterfApi {
+
+struct Api
+{
+ using Constructor = CMidDragDropInterf*(__thiscall*)(CMidDragDropInterf* thisptr,
+ const char* dialogName,
+ ITask* task,
+ CPhaseGame* phaseGame);
+ Constructor constructor;
+
+ /**
+ * This is not a scalar deleting destructor, its a common one.
+ * It does not free memory allocated for an object
+ */
+ using Destructor = void(__thiscall*)(CMidDragDropInterf* thisptr);
+ Destructor destructor;
+
+ using RemoveDropTarget = void(__thiscall*)(CMidDragDropInterf* thisptr,
+ IMidDropTarget* dropTarget);
+ RemoveDropTarget removeDropTarget;
+
+ using RemoveDropSource = void(__thiscall*)(CMidDragDropInterf* thisptr,
+ IMidDropSource* dropSource);
+ RemoveDropSource removeDropSource;
+};
+
+Api& get();
+
+} // namespace CMidDragDropInterfApi
+
} // namespace game
#endif // MIDDRAGDROPINTERF_H
diff --git a/mss32/include/middropmanager.h b/mss32/include/middropmanager.h
index 5387e92c..c04470f7 100644
--- a/mss32/include/middropmanager.h
+++ b/mss32/include/middropmanager.h
@@ -26,6 +26,7 @@ namespace game {
struct IMidDropManagerVftable;
struct IMidDropSource;
+struct IMidDropTarget;
struct IMidDropManager
{
@@ -40,6 +41,7 @@ struct IMidDropManagerVftable
using GetDropSource = IMidDropSource*(__thiscall*)(IMidDropManager* thisptr);
GetDropSource getDropSource;
+ /** Actual 'drop' action logic. */
using SetDropSource = void(__thiscall*)(IMidDropManager* thisptr,
IMidDropSource* value,
bool a3);
@@ -50,7 +52,23 @@ struct IMidDropManagerVftable
using ResetDropSource = void(__thiscall*)(IMidDropManager* thisptr);
ResetDropSource resetDropSource;
- void* methods[9];
+ void* method6;
+ void* method7;
+ void* method8;
+
+ void* method9;
+
+ using AddDropSource = void(__thiscall*)(IMidDropManager* thisptr, IMidDropSource* dropSource);
+ AddDropSource addDropSource;
+
+ using AddDropTarget = void(__thiscall*)(IMidDropManager* thisptr, IMidDropTarget* dropTarget);
+ AddDropTarget addDropTarget;
+
+ void* method12;
+ void* method13;
+
+ using IsShiftPressed = bool (*)();
+ IsShiftPressed isShiftPressed;
};
assert_vftable_size(IMidDropManagerVftable, 14);
diff --git a/mss32/include/middropsource.h b/mss32/include/middropsource.h
index 19e60f81..f5cebfcb 100644
--- a/mss32/include/middropsource.h
+++ b/mss32/include/middropsource.h
@@ -25,6 +25,8 @@
namespace game {
struct IMidDropSourceVftable;
+struct TypeDescriptor;
+struct IMidDropManager;
struct IMidDropSource
{
@@ -43,7 +45,11 @@ struct IMidDropSourceVftable
GetCursorHandle getCursorHandle;
void* method2;
- void* method3;
+
+ using HandleMouse = void(__thiscall*)(IMidDropSource* thisptr,
+ int mouseKey,
+ const CMqPoint* mousePosition);
+ HandleMouse handleMouse;
using IsPointOverButton = bool(__thiscall*)(IMidDropSource* thisptr, const CMqPoint* point);
IsPointOverButton isPointOverButton;
@@ -56,10 +62,12 @@ struct IMidDropSourceVftable
using Method7 = int(__thiscall*)(IMidDropSource* thisptr);
Method7 method7;
- void* method8;
+ using CastTo = void*(__thiscall*)(IMidDropSource* thisptr, const TypeDescriptor* type);
+ CastTo castTo;
- using Method9 = int(__thiscall*)(IMidDropSource* thisptr, bool buttonPressed);
- Method9 method9;
+ using GetDropManager = IMidDropManager*(__thiscall*)(IMidDropSource* thisptr,
+ bool buttonPressed);
+ GetDropManager getDropManager;
};
assert_vftable_size(IMidDropSourceVftable, 10);
diff --git a/mss32/include/midfreetask.h b/mss32/include/midfreetask.h
index dd7f51cd..afe4cb86 100644
--- a/mss32/include/midfreetask.h
+++ b/mss32/include/midfreetask.h
@@ -35,6 +35,19 @@ struct CMidFreeTask : public IMidTask
assert_size(CMidFreeTask, 16);
+namespace CMidFreeTaskApi {
+
+struct Api
+{
+ using Constructor = CMidFreeTask*(__thiscall*)(CMidFreeTask* thisptr,
+ CTaskManager* taskManager);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CMidFreeTaskApi
+
} // namespace game
#endif // MIDFREETASK_H
diff --git a/mss32/include/midgardid.h b/mss32/include/midgardid.h
index 2c5ea77d..76f1fdf2 100644
--- a/mss32/include/midgardid.h
+++ b/mss32/include/midgardid.h
@@ -141,6 +141,16 @@ static constexpr bool operator<(const CMidgardID& first, const CMidgardID& secon
extern const CMidgardID invalidId;
extern const CMidgardID emptyId;
+struct CMidgardIDHash
+{
+ std::size_t operator()(const game::CMidgardID& id) const
+ {
+ // All identifiers and their 32-bit value representaions must be unique.
+ // Use raw value as a hash.
+ return static_cast(id.value);
+ }
+};
+
namespace CMidgardIDApi {
struct Api
diff --git a/mss32/include/midgardmap.h b/mss32/include/midgardmap.h
index decf5cfd..2a7175ac 100644
--- a/mss32/include/midgardmap.h
+++ b/mss32/include/midgardmap.h
@@ -50,6 +50,12 @@ struct Api
IMidgardObjectMap* objectMap);
ChangeTerrain changeTerrain;
+ using GetTerrain = bool(__thiscall*)(const CMidgardMap* thisptr,
+ LTerrainCategory* terrain,
+ const CMqPoint* position,
+ const IMidgardObjectMap* objectMap);
+ GetTerrain getTerrain;
+
/** Returns ground type of map tile at specified position. */
using GetGround = bool(__thiscall*)(const CMidgardMap* thisptr,
LGroundCategory* ground,
diff --git a/mss32/include/midgardobjectmap.h b/mss32/include/midgardobjectmap.h
index c5f894dc..e4adb6f3 100644
--- a/mss32/include/midgardobjectmap.h
+++ b/mss32/include/midgardobjectmap.h
@@ -75,7 +75,7 @@ struct IMidgardObjectMapVftable
using GetObjectsTotal = int(__thiscall*)(const IMidgardObjectMap* thisptr);
GetObjectsTotal getObjectsTotal;
- using GetIterator = IteratorPtr*(__thiscall*)(IMidgardObjectMap* thisptr,
+ using GetIterator = IteratorPtr*(__thiscall*)(const IMidgardObjectMap* thisptr,
IteratorPtr* iterator);
/** Returns an iterator to the first record. */
diff --git a/mss32/include/midgardplan.h b/mss32/include/midgardplan.h
index 2a83b0d5..3403fad8 100644
--- a/mss32/include/midgardplan.h
+++ b/mss32/include/midgardplan.h
@@ -21,6 +21,7 @@
#define MIDGARDPLAN_H
#include "d2vector.h"
+#include "idlist.h"
#include "midgardid.h"
#include "midscenarioobject.h"
#include "mqpoint.h"
@@ -28,6 +29,8 @@
namespace game {
+struct IMapElement;
+
struct MidgardPlanElement
{
std::uint16_t y;
@@ -39,13 +42,35 @@ assert_size(MidgardPlanElement, 8);
using PlanElements = Vector;
+/**
+ * Element flags packing:
+ *
+ * +--+--+--+--+--+--+--+--+
+ * |S3|D3|S2|D2|S1|D1|S0|D0|
+ * +--+--+--+--+--+--+--+--+
+ * 7 6 5 4 3 2 1 0
+ *
+ * DN - bit indicating presence of a dynamic element on a tile with relative X position of N.
+ * SN - bit indicating presence of a static element on a tile with relative X position of N.
+ *
+ * Relative X position: (X & 3)
+ */
+
+/** Utility object used for mapping IMapElements coordinates to scenario object ids. */
struct CMidgardPlan : public IMidScenarioObject
{
CMidgardID unknownId;
int mapSize;
- char data[5184]; /**< 144 * 36. Accessed as: 36 * posY + (posX >> 2) */
- PlanElements elements;
- PlanElements elements2;
+ /**
+ * 36 x 144 array of bit flags indicating presence of a static or dynamic elements on a tile.
+ * Each array element stores flags of 4 adjacent tiles along X axis.
+ * Accessed as: 36 * Y + X / 4.
+ */
+ std::uint8_t elementFlags[5184];
+ /** Static map objects such as: Fort, Road, Landmark, Site, Ruin, Crystal, Location. */
+ PlanElements staticElements;
+ /** Dynamic objects: Stack, Bag, Tomb, Rod. */
+ PlanElements dynamicElements;
};
assert_size(CMidgardPlan, 5232);
@@ -69,6 +94,23 @@ struct Api
const IdType* objectTypes,
std::uint32_t typesTotal);
IsPositionContainsObjects isPositionContainsObjects;
+
+ /** Returns true if CMidSite object can be placed at specified position. */
+ using CanPlaceSite = bool(__stdcall*)(const CMqPoint* mapPosition,
+ const CMidgardPlan* plan,
+ const CMidgardID* siteId);
+ CanPlaceSite canPlaceSite;
+
+ /** Adds map element to plan. */
+ using AddMapElement = bool(__thiscall*)(CMidgardPlan* thisptr,
+ const IMapElement* mapElement,
+ bool unknown);
+ AddMapElement addMapElement;
+
+ using GetObjectsAtPoint = bool(__thiscall*)(const CMidgardPlan* thisptr,
+ IdList* objectIds,
+ const CMqPoint* mapPosition);
+ GetObjectsAtPoint getObjectsAtPoint;
};
Api& get();
diff --git a/mss32/include/miditemext.h b/mss32/include/miditemext.h
new file mode 100644
index 00000000..99d928a4
--- /dev/null
+++ b/mss32/include/miditemext.h
@@ -0,0 +1,32 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDITEMEXT_H
+#define MIDITEMEXT_H
+
+namespace game {
+
+struct IMidItemExt
+{
+ void* vftable;
+};
+
+} // namespace game
+
+#endif // MIDITEMEXT_H
diff --git a/mss32/include/midplayer.h b/mss32/include/midplayer.h
index 5502256a..b3367d9f 100644
--- a/mss32/include/midplayer.h
+++ b/mss32/include/midplayer.h
@@ -20,7 +20,7 @@
#ifndef MIDPLAYER_H
#define MIDPLAYER_H
-#include "categories.h"
+#include "aiattitudescat.h"
#include "currency.h"
#include "d2set.h"
#include "midgardid.h"
diff --git a/mss32/include/midserverlogic.h b/mss32/include/midserverlogic.h
index 0eeff320..c8df0e52 100644
--- a/mss32/include/midserverlogic.h
+++ b/mss32/include/midserverlogic.h
@@ -21,6 +21,7 @@
#define MIDSERVERLOGIC_H
#include "d2set.h"
+#include "idlist.h"
#include "idset.h"
#include "midmsgsender.h"
#include "midserverlogiccore.h"
@@ -32,6 +33,8 @@ struct CMidServer;
struct CStreamBits;
struct AiLogic;
struct CMidgardScenarioMap;
+struct IEventEffect;
+struct CMqPoint;
/*
* All the fields initially point to the same parent logic. Is this some kind of enumerable
@@ -85,10 +88,11 @@ struct CMidServerLogic
CStreamBits* streamBits;
int unknown7;
CMidServerLogicData2 data2;
- int unknown8;
+ int currentPlayerIndex;
int unknown9;
bool turnNumberIsZero;
char padding[3];
+ // Assumption: List> messagesAndStreams;
List list;
int unknown11;
int unknown12;
@@ -110,7 +114,7 @@ assert_offset(CMidServerLogic, data, 24);
assert_offset(CMidServerLogic, aiLogic, 36);
assert_offset(CMidServerLogic, playersIdList, 56);
assert_offset(CMidServerLogic, data2, 80);
-assert_offset(CMidServerLogic, unknown8, 244);
+assert_offset(CMidServerLogic, currentPlayerIndex, 244);
assert_offset(CMidServerLogic, unknown11, 272);
assert_offset(CMidServerLogic, list2, 300);
assert_offset(CMidServerLogic, unknown17, 316);
@@ -139,6 +143,84 @@ struct Api
const CMidgardID* toStackId,
const IdSet* itemIds);
StackExchangeItem stackExchangeItem;
+
+ using ApplyEventEffectsAndCheckMidEventTriggerers =
+ bool(__thiscall*)(CMidServerLogic** thisptr,
+ List* effectsList,
+ const CMidgardID* triggererId,
+ const CMidgardID* playingStackId);
+ ApplyEventEffectsAndCheckMidEventTriggerers applyEventEffectsAndCheckMidEventTriggerers;
+
+ using StackMove = bool(__thiscall*)(CMidServerLogic** thisptr,
+ const CMidgardID* playerId,
+ List>* movementPath,
+ const CMidgardID* stackId,
+ const CMqPoint* startingPoint,
+ const CMqPoint* endPoint);
+ StackMove stackMove;
+
+ using FilterAndProcessEventsNoPlayer = bool(__stdcall*)(IMidgardObjectMap* objectMap,
+ List* eventObjectList,
+ List* effectsList,
+ bool* stopProcessing,
+ IdList* executedEvents,
+ const CMidgardID* triggererStackId,
+ const CMidgardID* playingStackId);
+ FilterAndProcessEventsNoPlayer filterAndProcessEventsNoPlayer;
+
+ using CheckAndExecuteEvent = bool(__stdcall*)(IMidgardObjectMap* objectMap,
+ List* effectsList,
+ bool* stopProcessing,
+ const CMidgardID* eventId,
+ const CMidgardID* playerId,
+ const CMidgardID* stackTriggererId,
+ const CMidgardID* playingStackId,
+ int samePlayer);
+ CheckAndExecuteEvent checkAndExecuteEvent;
+
+ using ExecuteEventEffects = void(__stdcall*)(IMidgardObjectMap* objectMap,
+ List* effectsList,
+ bool* stopProcessing,
+ const CMidgardID* eventId,
+ const CMidgardID* playerId,
+ const CMidgardID* stackTriggererId,
+ const CMidgardID* playingStackId);
+ ExecuteEventEffects executeEventEffects;
+
+ using FilterAndProcessEvents = bool(__stdcall*)(IMidgardObjectMap* objectMap,
+ List* eventObjectList,
+ List* effectsList,
+ bool* stopProcessing,
+ IdList* executedEvents,
+ const CMidgardID* playerId,
+ const CMidgardID* triggererStackId,
+ const CMidgardID* playingStackId);
+ FilterAndProcessEvents filterAndProcessEvents;
+
+ using CheckEventConditions = bool(__stdcall*)(const IMidgardObjectMap* objectMap,
+ List* effectsList,
+ const CMidgardID* playerId,
+ const CMidgardID* stackTriggererId,
+ int samePlayer,
+ const CMidgardID* eventId);
+ CheckEventConditions checkEventConditions;
+
+ using Constructor = CMidServerLogic*(__thiscall*)(CMidServerLogic* thisptr,
+ CMidServer* server,
+ bool multiplayerGame,
+ bool hotseatGame,
+ int a5,
+ int gameVersion);
+ Constructor constructor;
+
+ using GetPlayerInfo = NetPlayerInfo*(__thiscall*)(CMidServerLogic* thisptr,
+ std::uint32_t playerNetId);
+ GetPlayerInfo getPlayerInfo;
+
+ /** Returns true if player with specified id is current: it actively plays its turn. */
+ using IsCurrentPlayer = bool(__stdcall*)(CMidServerLogic* serverLogic,
+ const CMidgardID* playerId);
+ IsCurrentPlayer isCurrentPlayer;
};
Api& get();
diff --git a/mss32/include/midserverlogiccore.h b/mss32/include/midserverlogiccore.h
index 9c823ca9..86f6948a 100644
--- a/mss32/include/midserverlogiccore.h
+++ b/mss32/include/midserverlogiccore.h
@@ -35,6 +35,7 @@ struct CMidServer;
struct IMidgardObjectMap;
struct NetMsgEntryData;
struct NetPlayerInfo;
+struct CMidEvent;
struct CMidServerLogicCoreData
{
@@ -47,7 +48,7 @@ struct CMidServerLogicCoreData
int gameVersion;
NetMsgEntryData** netMsgEntryData;
IMidgardObjectMap* objectMap;
- List list;
+ List eventObjectsList;
int unknown4;
std::uint32_t playerNetId;
Vector* players;
@@ -70,7 +71,7 @@ struct CMidServerLogicCoreData
};
assert_size(CMidServerLogicCoreData, 72);
-assert_offset(CMidServerLogicCoreData, list, 24);
+assert_offset(CMidServerLogicCoreData, eventObjectsList, 24);
assert_offset(CMidServerLogicCoreData, players, 48);
struct CMidServerLogicCore : public IMqNetTraffic
diff --git a/mss32/include/midserverlogichooks.h b/mss32/include/midserverlogichooks.h
index d58ed619..248ddff7 100644
--- a/mss32/include/midserverlogichooks.h
+++ b/mss32/include/midserverlogichooks.h
@@ -20,12 +20,19 @@
#ifndef MIDSERVERLOGICHOOKS_H
#define MIDSERVERLOGICHOOKS_H
+#include "d2pair.h"
#include "d2set.h"
+#include "idlist.h"
+#include "mqpoint.h"
namespace game {
struct IMidMsgSender;
struct CMidServerLogic;
-struct CMidgardID;
+struct IMidgardObjectMap;
+struct CMidEvent;
+struct IEventEffect;
+struct ITestCondition;
+struct CMidServer;
} // namespace game
namespace hooks {
@@ -37,6 +44,133 @@ bool __fastcall midServerLogicSendRefreshInfoHooked(const game::CMidServerLogic*
const game::Set* objectsList,
std::uint32_t playerNetId);
+bool __fastcall applyEventEffectsAndCheckMidEventTriggerersHooked(
+ game::CMidServerLogic** thisptr,
+ int /*%edx*/,
+ game::List* effectsList,
+ const game::CMidgardID* triggererId,
+ const game::CMidgardID* playingStackId);
+
+bool __fastcall stackMoveHooked(game::CMidServerLogic** thisptr,
+ int /*%edx*/,
+ const game::CMidgardID* playerId,
+ game::List>* movementPath,
+ const game::CMidgardID* stackId,
+ const game::CMqPoint* startingPoint,
+ const game::CMqPoint* endPoint);
+
+bool __stdcall filterAndProcessEventsNoPlayerHooked(game::IMidgardObjectMap* objectMap,
+ game::List* eventObjectList,
+ game::List* effectsList,
+ bool* stopProcessing,
+ game::IdList* executedEvents,
+ const game::CMidgardID* triggererStackId,
+ const game::CMidgardID* playingStackId);
+
+bool __stdcall filterAndProcessEventsHooked(game::IMidgardObjectMap* objectMap,
+ game::List* eventObjectList,
+ game::List* effectsList,
+ bool* stopProcessing,
+ game::IdList* executedEvents,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* triggererStackId,
+ const game::CMidgardID* playingStackId);
+
+bool __stdcall checkEventConditionsHooked(const game::IMidgardObjectMap* objectMap,
+ game::List* effectsList,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* stackTriggererId,
+ int samePlayer,
+ const game::CMidgardID* eventId);
+
+void __stdcall executeEventEffectsHooked(game::IMidgardObjectMap* objectMap,
+ game::List* effectsList,
+ bool* stopProcessing,
+ const game::CMidgardID* eventId,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* stackTriggererId,
+ const game::CMidgardID* playingStackId);
+
+bool __fastcall testFreqHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testLocationHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testEnterCityHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testLeaderToCityHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testOwnCityHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testDiplomacyHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testAllianceHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testLootRuinHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testTransformLandHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testVisitSiteHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testItemToLocationHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+bool __fastcall testVarInRangeHooked(const game::ITestCondition* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+game::CMidServerLogic* __fastcall midServerLogicCtorHooked(game::CMidServerLogic* thisptr,
+ int /*%edx*/,
+ game::CMidServer* server,
+ bool multiplayerGame,
+ bool hotseatGame,
+ int a5,
+ int gameVersion);
+
} // namespace hooks
#endif // MIDSERVERLOGICHOOKS_H
diff --git a/mss32/include/midsite.h b/mss32/include/midsite.h
index c372b736..e0ae76fd 100644
--- a/mss32/include/midsite.h
+++ b/mss32/include/midsite.h
@@ -31,6 +31,9 @@
namespace game {
+struct IMidgardObjectMap;
+struct CampaignStream;
+
/** Base class for site objects. */
struct CMidSite : public IMidScenarioObject
{
@@ -50,6 +53,42 @@ assert_size(CMidSite, 120);
assert_offset(CMidSite, mapElement, 8);
assert_offset(CMidSite, title, 52);
+struct CMidSiteVftable : public IMidScenarioObjectVftable
+{
+ using GetEntrancePoint = CMqPoint*(__thiscall*)(const CMidSite* thisptr, CMqPoint* entrance);
+ GetEntrancePoint getEntrancePoint;
+
+ using StreamSiteData = void(__thiscall*)(CMidSite* thisptr,
+ CampaignStream* stream,
+ const CMidgardID* siteId);
+ StreamSiteData streamSiteData;
+};
+
+assert_vftable_size(CMidSiteVftable, 6);
+
+namespace CMidSiteApi {
+
+struct Api
+{
+ using Constructor = CMidSite*(__thiscall*)(CMidSite* thisptr,
+ const CMidgardID* siteId,
+ const LSiteCategory* siteCategory);
+ Constructor constructor;
+
+ using SetData = bool(__thiscall*)(CMidSite* thisptr,
+ IMidgardObjectMap* objectMap,
+ int imgIso,
+ const char* imgIntf,
+ const CMqPoint* position,
+ const char* title,
+ const char* description);
+ SetData setData;
+};
+
+Api& get();
+
+} // namespace CMidSiteApi
+
} // namespace game
#endif // MIDSITE_H
diff --git a/mss32/include/midsitemage.h b/mss32/include/midsitemage.h
new file mode 100644
index 00000000..12925502
--- /dev/null
+++ b/mss32/include/midsitemage.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDSITEMAGE_H
+#define MIDSITEMAGE_H
+
+#include "idlist.h"
+#include "midsite.h"
+
+namespace game {
+
+/** Holds mage tower related data in scenario file and game. */
+struct CMidSiteMage : public CMidSite
+{
+ IdList spells;
+};
+
+assert_size(CMidSiteMage, 136);
+
+namespace CMidSiteMageApi {
+
+struct Api
+{
+ using Constructor = CMidSiteMage*(__thiscall*)(CMidSiteMage* thisptr, const CMidgardID* siteId);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CMidSiteMageApi
+
+} // namespace game
+
+#endif // MIDSITEMAGE_H
diff --git a/mss32/include/midsitemerchant.h b/mss32/include/midsitemerchant.h
new file mode 100644
index 00000000..c6c1705d
--- /dev/null
+++ b/mss32/include/midsitemerchant.h
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDSITEMERCHANT_H
+#define MIDSITEMERCHANT_H
+
+#include "d2list.h"
+#include "d2pair.h"
+#include "itemcategory.h"
+#include "midsite.h"
+
+namespace game {
+
+using ItemList = List>;
+
+/** Holds merchant related data in scenario file and game. */
+struct CMidSiteMerchant : public CMidSite
+{
+ ItemList items;
+ bool mission;
+ char padding[3];
+ Set canBuyItemCategories;
+};
+
+assert_size(CMidSiteMerchant, 168);
+
+namespace CMidSiteMerchantApi {
+
+struct Api
+{
+ using Constructor = CMidSiteMerchant*(__thiscall*)(CMidSiteMerchant* thisptr,
+ const CMidgardID* siteId);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CMidSiteMerchantApi
+
+} // namespace game
+
+#endif // MIDSITEMERCHANT_H
diff --git a/mss32/include/midsitemercs.h b/mss32/include/midsitemercs.h
new file mode 100644
index 00000000..ac54125e
--- /dev/null
+++ b/mss32/include/midsitemercs.h
@@ -0,0 +1,61 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDSITEMERCS_H
+#define MIDSITEMERCS_H
+
+#include "d2list.h"
+#include "midsite.h"
+
+namespace game {
+
+struct MercenaryUnit
+{
+ CMidgardID unitId;
+ bool unique;
+ char padding[3];
+};
+
+assert_size(MercenaryUnit, 8);
+
+/** Holds mercenary camp related data in scenario file and game. */
+struct CMidSiteMercs : public CMidSite
+{
+ List units;
+ int unknown;
+};
+
+assert_size(CMidSiteMercs, 140);
+
+namespace CMidSiteMercsApi {
+
+struct Api
+{
+ using Constructor = CMidSiteMercs*(__thiscall*)(CMidSiteMercs* thisptr,
+ const CMidgardID* siteId);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CMidSiteMercsApi
+
+} // namespace game
+
+#endif // MIDSITEMERCS_H
diff --git a/mss32/include/midsiteresourcemarket.h b/mss32/include/midsiteresourcemarket.h
new file mode 100644
index 00000000..25846730
--- /dev/null
+++ b/mss32/include/midsiteresourcemarket.h
@@ -0,0 +1,106 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDSITERESOURCEMARKET_H
+#define MIDSITERESOURCEMARKET_H
+
+#include "currency.h"
+#include "midsite.h"
+#include
+#include
+
+namespace game {
+struct alignas(8) TypeDescriptor;
+struct IMidgardObjectMap;
+} // namespace game
+
+namespace hooks {
+
+struct ExchangeRates
+{
+ game::CurrencyType resource2; /**< Resource to buy. */
+ int amount1; /**< Amount of resource1 needed to get amount2 of resource2. */
+ int amount2; /**< Amount of resource2 needed to get amount1 of resource1. */
+};
+
+struct ResourceExchange
+{
+ game::CurrencyType resource1; /**< Resource to sell. */
+ std::vector rates; /**< Resources to buy and exchange rates. */
+};
+
+using MarketExchangeRates = std::vector;
+
+union InfiniteStock
+{
+ struct
+ {
+ bool lifeMana : 1;
+ bool infernalMana : 1;
+ bool runicMana : 1;
+ bool deathMana : 1;
+ bool groveMana : 1;
+ bool gold : 1;
+ } parts;
+
+ std::uint8_t value;
+};
+
+assert_size(InfiniteStock, 1);
+
+struct CMidSiteResourceMarket : public game::CMidSite
+{
+ /** User defined exchange rates script, if set. */
+ std::string exchangeRatesScript;
+ /** Market resources, if finite. */
+ game::Bank stock;
+ /** Specifies whether certain resource types are infinite or not. */
+ InfiniteStock infiniteStock;
+ /** If true, resource market uses custom exchange rates script. */
+ bool customExchangeRates;
+};
+
+game::TypeDescriptor* getResourceMarketTypeDescriptor();
+
+CMidSiteResourceMarket* createResourceMarket(const game::CMidgardID* siteId);
+
+void addResourceMarketStreamRegister();
+
+bool isMarketStockInfinite(const InfiniteStock& stock, game::CurrencyType currency);
+
+bool getExchangeRates(const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID& marketId,
+ const game::CMidgardID& visitorStackId,
+ MarketExchangeRates& exchangeRates,
+ bool serverSide = false);
+
+const ExchangeRates* findExchangeRates(const MarketExchangeRates& marketRates,
+ game::CurrencyType playerCurrency,
+ game::CurrencyType marketCurrency);
+
+bool exchangeResources(game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID& marketId,
+ const game::CMidgardID& visitorStackId,
+ game::CurrencyType playerCurrency,
+ game::CurrencyType marketCurrency,
+ std::uint16_t amount);
+
+} // namespace hooks
+
+#endif // MIDSITERESOURCEMARKET_H
diff --git a/mss32/include/midsitetrainer.h b/mss32/include/midsitetrainer.h
new file mode 100644
index 00000000..2a227639
--- /dev/null
+++ b/mss32/include/midsitetrainer.h
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDSITETRAINER_H
+#define MIDSITETRAINER_H
+
+#include "midsite.h"
+
+namespace game {
+
+/** Represents training camp in scenario file and game. */
+struct CMidSiteTrainer : public CMidSite
+{ };
+
+assert_size(CMidSiteTrainer, 120);
+
+namespace CMidSiteTrainerApi {
+
+struct Api
+{
+ using Constructor = CMidSiteTrainer*(__thiscall*)(CMidSiteTrainer* thisptr,
+ const CMidgardID* siteId);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CMidSiteTrainerApi
+
+} // namespace game
+
+#endif // MIDSITETRAINER_H
diff --git a/mss32/include/midstack.h b/mss32/include/midstack.h
index 8daa4478..cb0b5bb0 100644
--- a/mss32/include/midstack.h
+++ b/mss32/include/midstack.h
@@ -94,6 +94,26 @@ assert_offset(CMidStack, inventory, 100);
assert_offset(CMidStack, leaderEquippedItems, 124);
assert_offset(CMidStack, orderTargetId, 172);
+struct CMidStackIMapElementVftable : public IMapElementVftable
+{
+ using Initialize = bool(__thiscall*)(IMapElement* thisptr,
+ const IMidgardObjectMap* objectMap,
+ const CMidgardID* leaderId,
+ const CMidgardID* ownerId,
+ const CMidgardID* subraceId,
+ const CMqPoint* position);
+ Initialize initialize;
+
+ /**
+ * Removes all units from stack, destroys all its items and sets leader,
+ * owner and subrace ids as empty.
+ */
+ using Cleanup = bool(__thiscall*)(IMapElement* thisptr, const IMidgardObjectMap* objectMap);
+ Cleanup cleanup;
+};
+
+assert_vftable_size(CMidStackIMapElementVftable, 3);
+
namespace CMidStackApi {
struct Api
@@ -113,7 +133,7 @@ struct Api
Api& get();
-const IMapElementVftable* vftable();
+const CMidStackIMapElementVftable* vftable();
} // namespace CMidStackApi
diff --git a/mss32/include/midstackdestroyed.h b/mss32/include/midstackdestroyed.h
new file mode 100644
index 00000000..e836d937
--- /dev/null
+++ b/mss32/include/midstackdestroyed.h
@@ -0,0 +1,46 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDSTACKDESTROYED_H
+#define MIDSTACKDESTROYED_H
+
+#include "d2list.h"
+#include "midscenarioobject.h"
+
+namespace game {
+
+struct MidStackDestroyedEntry
+{
+ CMidgardID stackId;
+ CMidgardID killerId;
+ CMidgardID stackSrcTemplateId;
+};
+
+assert_size(MidStackDestroyedEntry, 12);
+
+struct CMidStackDestroyed : public IMidScenarioObject
+{
+ List destroyedStacks;
+};
+
+assert_size(CMidStackDestroyed, 24);
+
+} // namespace game
+
+#endif // MIDSTACKDESTROYED_H
diff --git a/mss32/include/midtaskopeninterfparam.h b/mss32/include/midtaskopeninterfparam.h
new file mode 100644
index 00000000..42f8466c
--- /dev/null
+++ b/mss32/include/midtaskopeninterfparam.h
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDTASKOPENINTERFPARAM_H
+#define MIDTASKOPENINTERFPARAM_H
+
+#include "midfreetask.h"
+
+namespace game {
+
+template
+struct CMidTaskOpenInterfParam : public CMidFreeTask
+{
+ T* interf;
+};
+
+assert_size(CMidTaskOpenInterfParam, 20);
+
+} // namespace game
+
+#endif // MIDTASKOPENINTERFPARAM_H
diff --git a/mss32/include/midtaskopeninterfparamresmarket.h b/mss32/include/midtaskopeninterfparamresmarket.h
new file mode 100644
index 00000000..8a60dcd1
--- /dev/null
+++ b/mss32/include/midtaskopeninterfparamresmarket.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef MIDTASKOPENINTERFPARAMRESMARKET_H
+#define MIDTASKOPENINTERFPARAMRESMARKET_H
+
+namespace game {
+struct ITask;
+struct CTaskManager;
+struct CPhaseGame;
+struct CMidgardID;
+} // namespace game
+
+namespace hooks {
+
+/** Creates task that will show resource market interface when visited in game. */
+game::ITask* createMidTaskOpenInterfParamResMarket(game::CTaskManager* taskManager,
+ game::CPhaseGame* phaseGame,
+ const game::CMidgardID& visitorStackId,
+ const game::CMidgardID& siteId);
+} // namespace hooks
+
+#endif // MIDTASKOPENINTERFPARAMRESMARKET_H
diff --git a/mss32/include/midunitgroupadapter.h b/mss32/include/midunitgroupadapter.h
index 61fc39ca..d6352bb8 100644
--- a/mss32/include/midunitgroupadapter.h
+++ b/mss32/include/midunitgroupadapter.h
@@ -65,6 +65,22 @@ struct CMidUnitGroupAdapter : public IUnitGroup
assert_size(CMidUnitGroupAdapter, 8);
+namespace CMidUnitGroupAdapterApi {
+
+struct Api
+{
+ using Constructor = CMidUnitGroupAdapter*(__thiscall*)(CMidUnitGroupAdapter* thisptr,
+ IMidgardObjectMap* objectMap,
+ const CMidgardID* groupId,
+ const CMidgardID* playerId,
+ int leftSide);
+ Constructor constructor;
+};
+
+Api& get();
+
+}
+
} // namespace game
#endif // MIDUNITGROUPADAPTER_H
diff --git a/mss32/include/mqstream.h b/mss32/include/mqstream.h
index 0a46b9b4..26eb8d65 100644
--- a/mss32/include/mqstream.h
+++ b/mss32/include/mqstream.h
@@ -21,6 +21,7 @@
#define MQSTREAM_H
#include "d2assert.h"
+#include
namespace game {
@@ -43,7 +44,14 @@ struct CMqStreamVftable
using Serialize = void(__thiscall*)(CMqStream* thisptr, const void* data, int count);
Serialize serialize;
- void* methods[3];
+ using Method2 = int(__thiscall*)(CMqStream* thisptr);
+ Method2 method2;
+
+ using GetNumBytes = std::uint32_t(__thiscall*)(CMqStream* thisptr);
+ GetNumBytes getNumBytes;
+
+ using GetBuffer = void*(__thiscall*)(CMqStream* thisptr);
+ GetBuffer getBuffer;
};
assert_vftable_size(CMqStreamVftable, 5);
diff --git a/mss32/include/mquikernel.h b/mss32/include/mquikernel.h
index 98e5fe07..d47bcd36 100644
--- a/mss32/include/mquikernel.h
+++ b/mss32/include/mquikernel.h
@@ -21,7 +21,6 @@
#define MQUIKERNEL_H
#include "d2assert.h"
-#define WIN32_LEAN_AND_MEAN
#include
#include
diff --git a/mss32/include/nativegameinfo.h b/mss32/include/nativegameinfo.h
index 0d32ac65..eba6ca65 100644
--- a/mss32/include/nativegameinfo.h
+++ b/mss32/include/nativegameinfo.h
@@ -66,6 +66,7 @@ class NativeGameInfo final : public rsg::GameInfo
const rsg::SiteTexts& getMerchantTexts() const override;
const rsg::SiteTexts& getRuinTexts() const override;
const rsg::SiteTexts& getTrainerTexts() const override;
+ const rsg::SiteTexts& getMarketTexts() const override;
private:
bool readGameInfo(const std::filesystem::path& gameFolderPath);
@@ -115,6 +116,7 @@ class NativeGameInfo final : public rsg::GameInfo
rsg::SiteTexts merchantTexts;
rsg::SiteTexts ruinTexts;
rsg::SiteTexts trainerTexts;
+ rsg::SiteTexts marketTexts;
};
} // namespace hooks
diff --git a/mss32/include/netmsg.h b/mss32/include/netmsg.h
index c06abf76..6203190a 100644
--- a/mss32/include/netmsg.h
+++ b/mss32/include/netmsg.h
@@ -88,6 +88,9 @@ struct Api
{
using Destructor = void(__thiscall*)(CNetMsg* thisptr);
Destructor destructor;
+
+ using Serialize = void(__thiscall*)(CNetMsg* thisptr, CMqStream* stream);
+ Serialize serialize;
};
Api& get();
diff --git a/mss32/include/netmsgmapentry.h b/mss32/include/netmsgmapentry.h
index f9f8b760..78ea41b4 100644
--- a/mss32/include/netmsgmapentry.h
+++ b/mss32/include/netmsgmapentry.h
@@ -68,7 +68,9 @@ struct CNetMsgMapEntry_memberVftable;
struct CNetMsgMapEntry_member : public CNetMsgMapEntryT
{
void* data;
- bool(__thiscall* callback)(void* thisptr, CNetMsg* netMessage, std::uint32_t idFrom);
+
+ using Callback = bool(__thiscall*)(void* thisptr, CNetMsg* netMessage, std::uint32_t idFrom);
+ Callback callback;
};
assert_size(CNetMsgMapEntry_member, 12);
diff --git a/mss32/include/netmsgmapentryexchangeresourcesmsg.h b/mss32/include/netmsgmapentryexchangeresourcesmsg.h
new file mode 100644
index 00000000..f75e8744
--- /dev/null
+++ b/mss32/include/netmsgmapentryexchangeresourcesmsg.h
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef NETMSGMAPENTRYEXCHANGERESOURCESMSG_H
+#define NETMSGMAPENTRYEXCHANGERESOURCESMSG_H
+
+#include "netmsgmapentry.h"
+
+namespace game {
+struct CMidServerLogic;
+}
+
+namespace hooks {
+
+game::CNetMsgMapEntry_member* createNetMsgMapEntryExchangeResourcesMsg(
+ game::CMidServerLogic* serverLogic,
+ game::CNetMsgMapEntry_member::Callback callback);
+
+}
+
+#endif // NETMSGMAPENTRYEXCHANGERESOURCESMSG_H
diff --git a/mss32/include/nobleactioncat.h b/mss32/include/nobleactioncat.h
new file mode 100644
index 00000000..8e2a97dd
--- /dev/null
+++ b/mss32/include/nobleactioncat.h
@@ -0,0 +1,70 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef NOBLEACTIONCAT_H
+#define NOBLEACTIONCAT_H
+
+#include "categories.h"
+
+namespace game {
+
+struct LNobleActionCatTable : public CEnumConstantTable
+{ };
+
+struct LNobleActionCat : public Category
+{ };
+
+namespace NobleActionCategories {
+
+struct Categories
+{
+ LNobleActionCat* poisonStack;
+ LNobleActionCat* spy;
+ LNobleActionCat* stealItem;
+ LNobleActionCat* assassinate;
+ LNobleActionCat* misfit;
+ LNobleActionCat* duel;
+ LNobleActionCat* poisonCity;
+ LNobleActionCat* stealSpell;
+ LNobleActionCat* bribe;
+ LNobleActionCat* stealGold;
+ LNobleActionCat* riotCity;
+ LNobleActionCat* stealMerchant;
+ LNobleActionCat* stealMage;
+ LNobleActionCat* spyRuin;
+};
+
+Categories& get();
+
+} // namespace NobleActionCategories
+
+namespace LNobleActionCatTableApi {
+
+using Api = CategoryTableApi::Api;
+
+Api& get();
+
+/** Returns address of LNobleActionCatTable::vftable used in game. */
+const void* vftable();
+
+} // namespace LNobleActionCatTableApi
+
+} // namespace game
+
+#endif // NOBLEACTIONCAT_H
diff --git a/mss32/include/nobleactioncategoryset.h b/mss32/include/nobleactioncategoryset.h
new file mode 100644
index 00000000..fe770e79
--- /dev/null
+++ b/mss32/include/nobleactioncategoryset.h
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef NOBLEACTIONCATEGORYSET_H
+#define NOBLEACTIONCATEGORYSET_H
+
+#include "d2pair.h"
+#include "d2set.h"
+#include "nobleactioncat.h"
+
+namespace game {
+
+using NobleActionCatSet = Set;
+using NobleActionCatSetIterator = SetIterator;
+using NobleActionCatSetInsertIterator = Pair;
+
+namespace NobleActionCatSetApi {
+
+struct Api
+{
+ using Insert =
+ NobleActionCatSetInsertIterator*(__thiscall*)(NobleActionCatSet* thisptr,
+ NobleActionCatSetInsertIterator* result,
+ const LNobleActionCat* category);
+ Insert insert;
+};
+
+Api& get();
+
+} // namespace NobleActionCatSetApi
+
+} // namespace game
+
+#endif // NOBLEACTIONCATEGORYSET_H
diff --git a/mss32/include/nobleactionresult.h b/mss32/include/nobleactionresult.h
new file mode 100644
index 00000000..97e9cb58
--- /dev/null
+++ b/mss32/include/nobleactionresult.h
@@ -0,0 +1,82 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef NOBLEACTIONRESULT_H
+#define NOBLEACTIONRESULT_H
+
+#include "d2list.h"
+
+namespace game {
+
+struct INobleActionResultVftable;
+struct CMidgardID;
+struct IMidgardObjectMap;
+struct IEventEffect;
+struct LNobleActionCat;
+
+struct INobleActionResult
+{
+ INobleActionResultVftable* vftable;
+};
+
+assert_size(INobleActionResult, 4);
+
+struct INobleActionResultVftable
+{
+ using Destructor = void(__thiscall*)(INobleActionResult* thisptr, char flags);
+ Destructor destructor;
+
+ /** Returns integer value that is specific to action result. */
+ using GetValue = int(__thiscall*)(const INobleActionResult* thisptr);
+ GetValue getValue;
+
+ /** Returns id value that is specific to action result. */
+ using GetId = const CMidgardID*(__thiscall*)(const INobleActionResult* thisptr);
+ GetId getId;
+
+ /** Applies noble action result and returns true on success. */
+ using Apply = bool(__thiscall*)(INobleActionResult* thisptr,
+ IMidgardObjectMap* objectMap,
+ List* effects,
+ const CMidgardID* stackId,
+ const CMidgardID* targetObjectId);
+ Apply apply;
+};
+
+assert_vftable_size(INobleActionResultVftable, 4);
+
+namespace NobleActionsApi {
+
+struct Api
+{
+ /** Creates noble action result according to category specified. */
+ using Create = INobleActionResult*(__stdcall*)(IMidgardObjectMap* objectMap,
+ const LNobleActionCat* actionCategory,
+ const CMidgardID* targetObjectId,
+ const CMidgardID* id);
+ Create create;
+};
+
+Api& get();
+
+} // namespace NobleActionsApi
+
+} // namespace game
+
+#endif // NOBLEACTIONRESULT_H
diff --git a/mss32/include/nobleactionresultstealmarket.h b/mss32/include/nobleactionresultstealmarket.h
new file mode 100644
index 00000000..2990518a
--- /dev/null
+++ b/mss32/include/nobleactionresultstealmarket.h
@@ -0,0 +1,36 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef NOBLEACTIONRESULTSTEALMARKET_H
+#define NOBLEACTIONRESULTSTEALMARKET_H
+
+namespace game {
+struct INobleActionResult;
+struct IMidgardObjectMap;
+struct CMidgardID;
+} // namespace game
+
+namespace hooks {
+
+game::INobleActionResult* createStealMarketActionResult(const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID& marketId);
+
+} // namespace hooks
+
+#endif // NOBLEACTIONRESULTSTEALMARKET_H
diff --git a/mss32/include/notifyplayerlist.h b/mss32/include/notifyplayerlist.h
index 18b40709..5e88875d 100644
--- a/mss32/include/notifyplayerlist.h
+++ b/mss32/include/notifyplayerlist.h
@@ -36,7 +36,8 @@ struct INotifyPlayerListVftable
using Destructor = void(__thiscall*)(INotifyPlayerList* thisptr, char flags);
Destructor destructor;
- void* method1;
+ using Method1 = void(__thiscall*)(INotifyPlayerList* thisptr, int a2);
+ Method1 method1;
};
assert_vftable_size(INotifyPlayerListVftable, 2);
diff --git a/mss32/include/objectinterf.h b/mss32/include/objectinterf.h
index 9ecaa71c..7bce2e56 100644
--- a/mss32/include/objectinterf.h
+++ b/mss32/include/objectinterf.h
@@ -26,6 +26,8 @@ namespace game {
namespace editor {
+struct CTaskObj;
+
struct CObjectInterfData
{
int selectedMode;
@@ -41,6 +43,19 @@ struct CObjectInterf : public CIsoView
assert_size(CObjectInterf, 24);
+namespace CObjectInterfApi {
+
+struct Api
+{
+ // thisptr points to ITaskManagerHolder inside CObjectInterf
+ using CreateTaskObj = CTaskObj*(__thiscall*)(ITaskManagerHolder* thisptr);
+ CreateTaskObj createTaskObj;
+};
+
+Api& get();
+
+} // namespace CObjectInterfApi
+
} // namespace editor
} // namespace game
diff --git a/mss32/include/objectinterfhooks.h b/mss32/include/objectinterfhooks.h
new file mode 100644
index 00000000..62ed995c
--- /dev/null
+++ b/mss32/include/objectinterfhooks.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef OBJECTINTERFHOOKS_H
+#define OBJECTINTERFHOOKS_H
+
+namespace game {
+struct ITaskManagerHolder;
+
+namespace editor {
+struct CTaskObj;
+}
+
+} // namespace game
+
+namespace hooks {
+
+game::editor::CTaskObj* __fastcall createTaskObjHooked(game::ITaskManagerHolder* thisptr,
+ int /* %edx */);
+
+}
+
+#endif // OBJECTINTERFHOOKS_H
diff --git a/mss32/include/originalfunctions.h b/mss32/include/originalfunctions.h
index a71082ad..3a3ca89c 100644
--- a/mss32/include/originalfunctions.h
+++ b/mss32/include/originalfunctions.h
@@ -37,6 +37,9 @@
#include "exchangeinterf.h"
#include "game.h"
#include "gameimages.h"
+#include "globalvariables.h"
+#include "imagelayerlist.h"
+#include "mainview2.h"
#include "menubase.h"
#include "menuload.h"
#include "menunewskirmishhotseat.h"
@@ -49,12 +52,20 @@
#include "midevent.h"
#include "midgardscenariomap.h"
#include "midmsgsender.h"
+#include "midserverlogic.h"
#include "midunit.h"
#include "mqnetplayer.h"
#include "netmsg.h"
+#include "nobleactionresult.h"
+#include "objectinterf.h"
#include "pickupdropinterf.h"
+#include "scenedit.h"
+#include "scenpropinterf.h"
#include "sitemerchantinterf.h"
+#include "taskobjaddsite.h"
+#include "taskobjprop.h"
#include "testcondition.h"
+#include "visitors.h"
namespace hooks {
@@ -98,6 +109,7 @@ struct OriginalFunctions
game::ITestConditionApi::Api::Create createTestCondition;
game::CMidEventApi::Api::CheckValid checkEventValid;
game::BattleMsgDataApi::Api::BeforeBattleRound beforeBattleRound;
+ game::BattleMsgDataApi::Api::AiChooseBattleAction aiChooseBattleAction;
game::CMidUnitVftable::InitWithSoldierImpl initWithSoldierImpl;
game::CMidEvEffectApi::Api::CreateFromCategory createEventEffectFromCategory;
@@ -146,6 +158,51 @@ struct OriginalFunctions
game::CMidDataCache2::INotifyVftable::OnObjectChanged cityStackInterfOnObjectChanged;
game::CMidDataCache2::INotifyVftable::OnObjectChanged siteMerchantInterfOnObjectChanged;
+
+ game::editor::CScenPropInterfApi::Api::Constructor scenPropInterfCtor;
+
+ game::CMidServerLogicApi::Api::ApplyEventEffectsAndCheckMidEventTriggerers
+ applyEventEffectsAndCheckMidEventTriggerers;
+ game::CMidServerLogicApi::Api::StackMove stackMove;
+ game::CMidServerLogicApi::Api::FilterAndProcessEventsNoPlayer filterAndProcessEventsNoPlayer;
+ game::CMidServerLogicApi::Api::CheckAndExecuteEvent checkAndExecuteEvent;
+ game::CMidServerLogicApi::Api::FilterAndProcessEvents filterAndProcessEvents;
+ game::CMidServerLogicApi::Api::CheckEventConditions checkEventConditions;
+ game::CMidServerLogicApi::Api::ExecuteEventEffects executeEventEffects;
+
+ game::ITestConditionVftable::Test testFrequency;
+ game::ITestConditionVftable::Test testLocation;
+ game::ITestConditionVftable::Test testEnterCity;
+ game::ITestConditionVftable::Test testLeaderToCity;
+ game::ITestConditionVftable::Test testOwnCity;
+ game::ITestConditionVftable::Test testDiplomacy;
+ game::ITestConditionVftable::Test testAlliance;
+ game::ITestConditionVftable::Test testLootRuin;
+ game::ITestConditionVftable::Test testTransformLand;
+ game::ITestConditionVftable::Test testVisitSite;
+ game::ITestConditionVftable::Test testItemToLocation;
+ game::ITestConditionVftable::Test testVarInRange;
+
+ game::RemoveStack removeStack;
+ game::VisitorApi::Api::SetStackSrcTemplate setStackSrcTemplate;
+
+ game::editor::CObjectInterfApi::Api::CreateTaskObj createTaskObj;
+
+ game::ImageLayerListApi::Api::GetMapElementIsoLayerImages getMapElementIsoLayerImages;
+ game::editor::CTaskObjVftable::DoAction taskObjPropDoAction;
+ game::editor::CTaskObjVftable::DoAction taskObjAddSiteDoAction;
+ game::CScenEditApi::Api::ReadScenData readScenData;
+
+ game::CMainView2Api::Api::HandleCmdStackVisitMsg handleCmdStackVisitMsg;
+
+ game::CMidServerLogicApi::Api::Constructor midServerLogicCtor;
+
+ game::NobleActionsApi::Api::Create createNobleActionResult;
+ game::GetNobleActions getSiteNobleActions;
+ game::GetNobleActions getPossibleNobleActions;
+ game::GetNobleActionResultDescription getNobleActionResultDescription;
+
+ game::GlobalVariablesApi::Api::Constructor globalVariablesCtor;
};
OriginalFunctions& getOriginalFunctions();
diff --git a/mss32/include/paperdollchildinterf.h b/mss32/include/paperdollchildinterf.h
new file mode 100644
index 00000000..dd6ddd00
--- /dev/null
+++ b/mss32/include/paperdollchildinterf.h
@@ -0,0 +1,64 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef PAPERDOLLCHILDINTERF_H
+#define PAPERDOLLCHILDINTERF_H
+
+#include "interface.h"
+#include "midgardid.h"
+
+namespace game {
+
+struct CDialogInterf;
+struct CMidDragDropInterf;
+struct CDDEquipmentGroup;
+struct CPhaseGame;
+
+struct CPaperdollChildInterf : public CInterface
+{
+ CDialogInterf* paperdollDialog;
+ CMidDragDropInterf* dragDropInterface;
+ Vector leaderItemAreas;
+ CDDEquipmentGroup* equipmentGroup;
+ CMidgardID stackId;
+ CPhaseGame* phaseGame;
+};
+
+assert_size(CPaperdollChildInterf, 44);
+
+namespace CPaperdollChildInterfApi {
+
+struct Api
+{
+ using Constructor = CPaperdollChildInterf*(__thiscall*)(CPaperdollChildInterf* thisptr,
+ CMidDragDropInterf* dragDrop,
+ CPhaseGame* phaseGame,
+ const CMidgardID* stackId,
+ CInterface* parentInterface,
+ const CMqRect* paperdollArea);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CPaperdollChildInterfApi
+
+} // namespace game
+
+#endif // PAPERDOLLCHILDINTERF_H
diff --git a/mss32/include/phase.h b/mss32/include/phase.h
index bbb29ded..ae81f80c 100644
--- a/mss32/include/phase.h
+++ b/mss32/include/phase.h
@@ -25,9 +25,11 @@
namespace game {
struct CMidClient;
-struct IMidgardObjectMap;
+struct CMidDataCache2;
struct CMidgardID;
struct CInterface;
+struct CMidCommandQueue2;
+struct CEncParamBase;
struct CPhaseData
{
@@ -49,11 +51,17 @@ namespace CPhaseApi {
struct Api
{
- using GetObjectMap = IMidgardObjectMap*(__thiscall*)(CPhase* thisptr);
- GetObjectMap getObjectMap;
+ using GetObjectMap = CMidDataCache2*(__thiscall*)(CPhase* thisptr);
+ GetObjectMap getDataCache;
using GetCurrentPlayerId = const CMidgardID*(__thiscall*)(CPhase* thisptr);
GetCurrentPlayerId getCurrentPlayerId;
+
+ using GetCommandQueue = CMidCommandQueue2*(__thiscall*)(CPhase* thisptr);
+ GetCommandQueue getCommandQueue;
+
+ using ShowEncyclopediaPopup = void(__thiscall*)(CPhase* thisptr, const CEncParamBase* encParam);
+ ShowEncyclopediaPopup showEncyclopediaPopup;
};
Api& get();
diff --git a/mss32/include/phasegame.h b/mss32/include/phasegame.h
index 016ced26..af8b5cae 100644
--- a/mss32/include/phasegame.h
+++ b/mss32/include/phasegame.h
@@ -45,7 +45,8 @@ struct CPhaseGameData
SmartPointer palMapIsoScroller;
IIsoCBScroll* audioRegionCtrl;
CMidClient* midClient;
- int unknown8;
+ bool clientTakesTurn;
+ char padding[3];
CMidObjectNotify* midObjectNotify;
CMidAnim2System* animSystem;
void* listPtr;
diff --git a/mss32/include/pictureinterf.h b/mss32/include/pictureinterf.h
index a86c8736..c3cd0ebf 100644
--- a/mss32/include/pictureinterf.h
+++ b/mss32/include/pictureinterf.h
@@ -63,6 +63,11 @@ struct Api
const CMqPoint* offset);
SetImage setImage;
+ using SetImageWithAnchor = void(__thiscall*)(CPictureInterf* thisptr,
+ IMqImage2* image,
+ char anchor);
+ SetImageWithAnchor setImageWithAnchor;
+
/** Assigns mouse button press functor. */
using AssignFunctor = void(__thiscall*)(CPictureInterf* thisptr, SmartPointer* functor);
AssignFunctor assignFunctor;
diff --git a/mss32/include/raceset.h b/mss32/include/raceset.h
index 6bc40bb8..7b4fcf60 100644
--- a/mss32/include/raceset.h
+++ b/mss32/include/raceset.h
@@ -43,6 +43,9 @@ struct Api
RaceSetIterator* iterator,
LRaceCategory* raceCategory);
Add add;
+
+ using Find = SetIterator* (__thiscall*)(const RaceSet* thisptr, SetIterator* iterator, const LRaceCategory* raceCategory);
+ Find find;
};
Api& get();
diff --git a/mss32/include/resetstackext.h b/mss32/include/resetstackext.h
index 3bef0f7d..3c0e6c25 100644
--- a/mss32/include/resetstackext.h
+++ b/mss32/include/resetstackext.h
@@ -20,6 +20,8 @@
#ifndef RESETSTACKEXT_H
#define RESETSTACKEXT_H
+#include "idvector.h"
+
namespace game {
struct IResetStackExtVftable;
@@ -34,7 +36,23 @@ struct IResetStackExtVftable
using Destructor = void(__thiscall*)(IResetStackExt* thisptr, bool freeMemory);
Destructor destructor;
- void* methods[4];
+ using HireLeader = bool(
+ __thiscall*)(IResetStackExt* thisptr, const CMidgardID* unitImplId, int a3, int a4, int a5);
+ HireLeader hireLeader;
+
+ void* methods[2];
+
+ /**
+ * Returns ids of units or leaders that can be hired.
+ * @param[in] thisptr object pointer.
+ * @param[inout] unitImplIds vector where ids will be stored.
+ * @param leaders - if set to 1, leader ids will be returned.
+ * Any other value results in return of unit ids. Game logic uses 2 for units here.
+ */
+ using GetUnitIdsForHire = void(__thiscall*)(IResetStackExt* thisptr,
+ IdVector* unitImplIds,
+ int leaders);
+ GetUnitIdsForHire getUnitIdsForHire;
using GetStackId = CMidgardID*(__thiscall*)(IResetStackExt* thisptr, CMidgardID* value);
GetStackId getStackId;
diff --git a/mss32/include/resourcemarketinterface.h b/mss32/include/resourcemarketinterface.h
new file mode 100644
index 00000000..bd3bab59
--- /dev/null
+++ b/mss32/include/resourcemarketinterface.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef RESOURCEMARKETINTERFACE_H
+#define RESOURCEMARKETINTERFACE_H
+
+namespace game {
+struct CInterface;
+
+namespace editor {
+struct CTaskObjProp;
+}
+} // namespace game
+
+namespace hooks {
+struct CMidSiteResourceMarket;
+
+/** Create resource market interface to show in Scenario Editor. */
+game::CInterface* createResourceMarketInterface(game::editor::CTaskObjProp* task,
+ CMidSiteResourceMarket* market);
+
+} // namespace hooks
+
+#endif // RESOURCEMARKETINTERFACE_H
diff --git a/mss32/include/resourcetype.h b/mss32/include/resourcetype.h
new file mode 100644
index 00000000..a1479a51
--- /dev/null
+++ b/mss32/include/resourcetype.h
@@ -0,0 +1,51 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef RESOURCETYPE_H
+#define RESOURCETYPE_H
+
+#include "categories.h"
+
+namespace game {
+
+struct LResourceTypeTable : public CEnumConstantTable
+{ };
+
+struct LResourceType : public Category
+{ };
+
+namespace ResourceTypes {
+
+struct Categories
+{
+ LResourceType* gold;
+ LResourceType* infernalMana;
+ LResourceType* lifeMana;
+ LResourceType* deathMana;
+ LResourceType* runicMana;
+ LResourceType* groveMana;
+};
+
+Categories& get();
+
+} // namespace ResourceTypes
+
+} // namespace game
+
+#endif // RESOURCETYPE_H
diff --git a/mss32/include/scenedit.h b/mss32/include/scenedit.h
index 11d2de98..0e1fa8ed 100644
--- a/mss32/include/scenedit.h
+++ b/mss32/include/scenedit.h
@@ -94,6 +94,10 @@ struct Api
{
using Instance = CScenEdit*(__cdecl*)();
Instance instance;
+
+ /** Reads contents of dbf files from ScenData folder. */
+ using ReadScenData = bool(__thiscall*)(CScenEdit* thisptr);
+ ReadScenData readScenData;
};
Api& get();
diff --git a/mss32/include/scenedithooks.h b/mss32/include/scenedithooks.h
new file mode 100644
index 00000000..d11325f2
--- /dev/null
+++ b/mss32/include/scenedithooks.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef SCENEDITHOOKS_H
+#define SCENEDITHOOKS_H
+
+#include
+#include
+#include
+
+namespace game {
+struct CScenEdit;
+}
+
+namespace hooks {
+
+using NameDescPair = std::pair;
+using MarketNames = std::vector;
+
+bool __fastcall readScenDataHooked(game::CScenEdit* thisptr, int /*%edx*/);
+
+const MarketNames& getMarketNames();
+
+}
+
+#endif // SCENEDITHOOKS_H
diff --git a/mss32/include/scenpropinterf.h b/mss32/include/scenpropinterf.h
new file mode 100644
index 00000000..7dc2ea10
--- /dev/null
+++ b/mss32/include/scenpropinterf.h
@@ -0,0 +1,76 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef SCENPROPINTERF_H
+#define SCENPROPINTERF_H
+
+#include "popupinterf.h"
+
+namespace game {
+struct CSpinButtonInterf;
+
+namespace editor {
+
+struct CScenPropInterfData
+{
+ char unknown[156];
+};
+
+/** Represents DLG_PROP from ScenEdit.dlg. */
+struct CScenPropInterf : public CPopupInterf
+{
+ CScenPropInterfData* data;
+};
+
+assert_size(CScenPropInterf, 28);
+
+namespace CScenPropInterfApi {
+
+struct Api
+{
+ using Constructor = CScenPropInterf*(__thiscall*)(CScenPropInterf* thisptr,
+ ITask* task,
+ char* a3);
+ Constructor constructor;
+
+ struct SpinButtonCallback
+ {
+ using Callback = void(__thiscall*)(void* thisptr, CSpinButtonInterf* spinButton);
+
+ Callback callback;
+ int unknown;
+ };
+
+ /** Reuse function from CCapitalInterf. */
+ using CreateSpinButtonFunctor = SmartPointer*(__stdcall*)(SmartPointer* functor,
+ int dummy,
+ void* interf,
+ SpinButtonCallback* callback);
+ CreateSpinButtonFunctor createSpinButtonFunctor;
+};
+
+Api& get();
+
+} // namespace CScenPropInterfApi
+
+} // namespace editor
+
+} // namespace game
+
+#endif // SCENPROPINTERF_H
diff --git a/mss32/include/scenpropinterfhooks.h b/mss32/include/scenpropinterfhooks.h
new file mode 100644
index 00000000..4261b27f
--- /dev/null
+++ b/mss32/include/scenpropinterfhooks.h
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef SCENPROPINTERFHOOKS_H
+#define SCENPROPINTERFHOOKS_H
+
+namespace game {
+struct ITask;
+
+namespace editor {
+struct CScenPropInterf;
+}
+
+} // namespace game
+
+namespace hooks {
+
+game::editor::CScenPropInterf* __fastcall scenPropInterfCtorHooked(
+ game::editor::CScenPropInterf* thisptr,
+ int /*%edx*/,
+ game::ITask* task,
+ char* a3);
+}
+
+#endif // SCENPROPINTERFHOOKS_H
diff --git a/mss32/include/settings.h b/mss32/include/settings.h
index a3e0e881..7a0e00c0 100644
--- a/mss32/include/settings.h
+++ b/mss32/include/settings.h
@@ -20,10 +20,14 @@
#ifndef SETTINGS_H
#define SETTINGS_H
+#include
#include
#include
#include
-#include
+
+namespace game {
+enum class BattleAction : int;
+}
namespace hooks {
@@ -78,7 +82,7 @@ struct Settings
bool freeTransformSelfAttack;
bool freeTransformSelfAttackInfinite;
bool fixEffectiveHpFormula;
-
+
struct AdditionalLordIncome
{
int warrior = 0;
@@ -172,6 +176,8 @@ struct Settings
struct Battle
{
+ game::BattleAction fallbackAction;
+ bool debugAi{false};
bool allowRetreatedUnitsToUpgrade{false};
bool carryXpOverUpgrade{false};
bool allowMultiUpgrade{false};
diff --git a/mss32/include/sitecategoryhooks.h b/mss32/include/sitecategoryhooks.h
new file mode 100644
index 00000000..f166a9a2
--- /dev/null
+++ b/mss32/include/sitecategoryhooks.h
@@ -0,0 +1,44 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef SITECATEGORYHOOKS_H
+#define SITECATEGORYHOOKS_H
+
+#include "sitecategories.h"
+#include
+
+namespace hooks {
+
+struct CustomSiteCategories
+{
+ std::filesystem::path exchangeRatesScript;
+ game::LSiteCategory resourceMarket;
+ bool exists{};
+};
+
+CustomSiteCategories& customSiteCategories();
+
+game::LSiteCategoryTable* __fastcall siteCategoryTableCtorHooked(game::LSiteCategoryTable* thisptr,
+ int /*%edx*/,
+ const char* globalsFolderPath,
+ void* codeBaseEnvProxy);
+
+} // namespace hooks
+
+#endif // SITECATEGORYHOOKS_H
diff --git a/mss32/include/sitemerchantinterf.h b/mss32/include/sitemerchantinterf.h
index 72c3a710..18832267 100644
--- a/mss32/include/sitemerchantinterf.h
+++ b/mss32/include/sitemerchantinterf.h
@@ -50,6 +50,7 @@ struct CSiteMerchantInterf : public CMidDataCache2::INotify
};
assert_size(CSiteMerchantInterf, 40);
+assert_offset(CSiteMerchantInterf, data, 36);
namespace CSiteMerchantInterfApi {
diff --git a/mss32/include/siteresourcemarketinterf.h b/mss32/include/siteresourcemarketinterf.h
new file mode 100644
index 00000000..e9b6b335
--- /dev/null
+++ b/mss32/include/siteresourcemarketinterf.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef SITERESOURCEMARKETINTERF_H
+#define SITERESOURCEMARKETINTERF_H
+
+namespace game {
+struct CInterface;
+struct ITask;
+struct CPhaseGame;
+struct CMidgardID;
+} // namespace game
+
+namespace hooks {
+
+/** Create resource market interface to show in game. */
+game::CInterface* createSiteResourceMarketInterf(game::ITask* task,
+ game::CPhaseGame* phaseGame,
+ const game::CMidgardID& visitorStackId,
+ const game::CMidgardID& marketId);
+
+} // namespace hooks
+
+#endif // SITERESOURCEMARKETINTERF_H
diff --git a/mss32/include/sounds.h b/mss32/include/sounds.h
index 34ec36b4..fc9f47c8 100644
--- a/mss32/include/sounds.h
+++ b/mss32/include/sounds.h
@@ -29,6 +29,56 @@ struct CWavStore;
struct Wdb;
struct CLogFile;
+enum class SoundEffect : int
+{
+ Appear,
+ Boatsnd,
+ Entrsite,
+ Entrruin,
+ Entrcity,
+ Exitcity,
+ Occupy,
+ Spelldis,
+ Lootruin,
+ Enroll,
+ Occupy2,
+ Beep,
+ Botreprt,
+ Seebat,
+ Sbattle,
+ Snoble,
+ Stolen,
+ Building,
+ Openbook,
+ Closbook,
+ Bkpopup,
+ Openintr,
+ Closintr,
+ Pboost,
+ Pheal,
+ Previve,
+ Useitem,
+ Buyitem,
+ Citygrow,
+ Takebag,
+ Spinrock,
+ Chngface,
+ Soundfx,
+ Givegold,
+ Tradspel,
+ Reftrspe,
+ Traditem,
+ Reftritm,
+ Alliance,
+ Refallia,
+ Brkallia,
+ Aichat,
+ AUNN7778,
+ AUNN7788,
+ Endriot,
+ Creatstk,
+};
+
struct SoundsData
{
String string;
@@ -48,6 +98,29 @@ struct Sounds
assert_size(Sounds, 4);
+using SoundsPtr = SmartPtr;
+
+namespace SoundsApi {
+
+struct Api
+{
+ using Instance = SoundsPtr*(__stdcall*)(SoundsPtr* sounds);
+ Instance instance;
+
+ using SoundsPtrSetData = void(__thiscall*)(SoundsPtr* thisptr, Sounds* data);
+ SoundsPtrSetData soundsPtrSetData;
+
+ using PlaySound = int(__thiscall*)(Sounds* thisptr,
+ SoundEffect effect,
+ int a2,
+ SmartPointer* functor);
+ PlaySound playSound;
+};
+
+Api& get();
+
+} // namespace SoundsApi
+
} // namespace game
#endif // SOUNDS_H
diff --git a/mss32/include/stacktemplatecache.h b/mss32/include/stacktemplatecache.h
new file mode 100644
index 00000000..646e8e60
--- /dev/null
+++ b/mss32/include/stacktemplatecache.h
@@ -0,0 +1,49 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef STACKTEMPLATECACHE_H
+#define STACKTEMPLATECACHE_H
+
+#include "midgardid.h"
+#include
+
+namespace hooks {
+
+using StacksSet = std::unordered_set;
+
+/** Adds mapping of stack template and a stack created from it. */
+void stackTemplateCacheAdd(const game::CMidgardID& stackTemplateId,
+ const game::CMidgardID& stackId);
+
+/** Removes existing stack template and stack mapping. */
+void stackTemplateCacheRemove(const game::CMidgardID& stackTemplateId,
+ const game::CMidgardID& stackId);
+
+/** Returns stacks created from template or nullptr if no stacks were created. */
+const StacksSet* stackTemplateCacheFind(const game::CMidgardID& stackTemplateId);
+
+/** Returns true if at least 1 stack was created from specified template. */
+bool stackTemplateCacheCheck(const game::CMidgardID& stackTemplateId);
+
+/** Clears entire cache. */
+void stackTemplateCacheClear();
+
+} // namespace hooks
+
+#endif // STACKTEMPLATECACHE_H
diff --git a/mss32/include/streambits.h b/mss32/include/streambits.h
new file mode 100644
index 00000000..cbcfe5a8
--- /dev/null
+++ b/mss32/include/streambits.h
@@ -0,0 +1,66 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef STREAMBITS_H
+#define STREAMBITS_H
+
+#include "mqstream.h"
+
+namespace game {
+
+struct CMidgardID;
+
+struct CStreamBits : public CMqStream
+{
+ void* buffer;
+ int bufferSize;
+ int unknown4;
+ char unknown5;
+ char unknown6;
+ char padding[2];
+};
+
+assert_size(CStreamBits, 24);
+
+namespace CStreamBitsApi {
+
+struct Api
+{
+ using SerializeId = bool(__stdcall*)(CStreamBits* stream, CMidgardID* id);
+ SerializeId serializeId;
+
+ /** Create CStreamBits object in a read mode. */
+ using ReadConstructor = CStreamBits*(__thiscall*)(CStreamBits* thisptr,
+ int dummy,
+ void* buffer,
+ std::uint32_t bufferSize,
+ bool a5);
+ ReadConstructor readConstructor;
+
+ using Destructor = void(__thiscall*)(CStreamBits* thisptr);
+ Destructor destructor;
+};
+
+Api& get();
+
+} // namespace CStreamBitsApi
+
+} // namespace game
+
+#endif // STREAMBITS_H
diff --git a/mss32/include/taskmanager.h b/mss32/include/taskmanager.h
index 750ff141..f304d35a 100644
--- a/mss32/include/taskmanager.h
+++ b/mss32/include/taskmanager.h
@@ -25,6 +25,7 @@
namespace game {
struct ITaskManagerHolder;
+struct ITask;
struct CTaskManagerData
{
@@ -42,6 +43,18 @@ struct CTaskManager
assert_size(CTaskManager, 8);
+namespace CTaskManagerApi {
+
+struct Api
+{
+ using SetCurrentTask = void(__thiscall*)(CTaskManager* taskManager, ITask* task);
+ SetCurrentTask setCurrentTask;
+};
+
+Api& get();
+
+} // namespace CTaskManagerApi
+
} // namespace game
#endif // TASKMANAGER_H
diff --git a/mss32/include/taskobj.h b/mss32/include/taskobj.h
new file mode 100644
index 00000000..4a701d48
--- /dev/null
+++ b/mss32/include/taskobj.h
@@ -0,0 +1,81 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TASKOBJ_H
+#define TASKOBJ_H
+
+#include "canselect.h"
+#include "taskbase.h"
+
+namespace game {
+
+struct CursorHandle;
+
+namespace editor {
+
+struct CObjectInterf;
+
+struct CTaskObjData
+{
+ CObjectInterf* objectInterf;
+ int unknown2;
+ int unknown3;
+ int unknown4;
+};
+
+assert_size(CTaskObjData, 16);
+
+/** Base class for tasks related to scenario objects. */
+struct CTaskObj : public CTaskBase
+{
+ ICanSelect canSelect;
+ CTaskObjData* taskObjData;
+};
+
+assert_size(CTaskObj, 28);
+
+struct CTaskObjVftable : public ITaskVftable
+{
+ /** Returns brush size when the task is active. */
+ using GetBrushSize = CMqPoint*(__thiscall*)(const CTaskObj* thisptr, CMqPoint* size);
+ GetBrushSize getBrushSize;
+
+ using UnknownMethod = int(__thiscall*)(CTaskObj* thisptr, int a2);
+ UnknownMethod unknown;
+
+ using DoAction = bool(__thiscall*)(CTaskObj* thisptr, const CMqPoint* mapPosition);
+ /** Logic depends on child class. Creates scenario objects or deletes them, moves etc. */
+ DoAction doAction;
+
+ /** Returns true if action can be performed. */
+ DoAction checkActionPossible;
+
+ using GetCursor = SmartPtr*(__thiscall*)(const CTaskObj* thisptr,
+ SmartPtr* cursor,
+ bool selectionAllowed);
+ GetCursor getCursor;
+};
+
+assert_vftable_size(CTaskObjVftable, 11);
+
+} // namespace editor
+
+} // namespace game
+
+#endif // TASKOBJ_H
diff --git a/mss32/include/taskobjaddsite.h b/mss32/include/taskobjaddsite.h
new file mode 100644
index 00000000..0c131e59
--- /dev/null
+++ b/mss32/include/taskobjaddsite.h
@@ -0,0 +1,69 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TASKOBJADDSITE_H
+#define TASKOBJADDSITE_H
+
+#include "sitecategories.h"
+#include "taskobj.h"
+
+namespace game {
+
+namespace editor {
+
+struct CObjectInterf;
+
+struct CTaskObjAddSiteData
+{
+ LSiteCategory siteCategory;
+ SmartPtr selectUnitCursor;
+ SmartPtr noDragDropCursor;
+};
+
+assert_size(CTaskObjAddSiteData, 28);
+
+/** Creates site objects on scenario map. */
+struct CTaskObjAddSite : public CTaskObj
+{
+ CTaskObjAddSiteData* siteData;
+};
+
+assert_size(CTaskObjAddSite, 32);
+
+namespace CTaskObjAddSiteApi {
+
+struct Api
+{
+ using Constructor = CTaskObjAddSite*(__thiscall*)(CTaskObjAddSite* thisptr,
+ CObjectInterf* objInterf,
+ LSiteCategory category);
+ Constructor constructor;
+
+ CTaskObjVftable::DoAction doAction;
+};
+
+Api& get();
+
+} // namespace CTaskObjAddSiteApi
+
+} // namespace editor
+
+} // namespace game
+
+#endif // TASKOBJADDSITE_H
diff --git a/mss32/include/taskobjaddsitehooks.h b/mss32/include/taskobjaddsitehooks.h
new file mode 100644
index 00000000..02ab57ed
--- /dev/null
+++ b/mss32/include/taskobjaddsitehooks.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TASKOBJADDSITEHOOKS_H
+#define TASKOBJADDSITEHOOKS_H
+
+namespace game {
+struct CMqPoint;
+
+namespace editor {
+struct CTaskObjAddSite;
+}
+} // namespace game
+
+namespace hooks {
+
+bool __fastcall taskObjAddSiteDoActionHooked(game::editor::CTaskObjAddSite* thisptr,
+ int /*%edx*/,
+ const game::CMqPoint* mapPosition);
+
+}
+
+#endif // TASKOBJADDSITEHOOKS_H
diff --git a/mss32/include/taskobjerase.h b/mss32/include/taskobjerase.h
new file mode 100644
index 00000000..6067b453
--- /dev/null
+++ b/mss32/include/taskobjerase.h
@@ -0,0 +1,68 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TASKOBJERASE_H
+#define TASKOBJERASE_H
+
+#include "taskobj.h"
+
+namespace game {
+
+namespace editor {
+
+struct CTaskObjEraseData
+{
+ char unknown[12];
+ SmartPointer ptr;
+ SmartPointer ptr2;
+ int unknown2;
+ int unknown3;
+ int unknown4;
+ int unknown5;
+ int unknown6;
+ int unknown7;
+};
+
+assert_size(CTaskObjEraseData, 52);
+
+struct CTaskObjErase : public CTaskObj
+{
+ CTaskObjEraseData* taskEraseData;
+};
+
+assert_size(CTaskObjErase, 32);
+
+namespace CTaskObjEraseApi {
+
+struct Api
+{
+ using Constructor = CTaskObjErase*(__thiscall*)(CTaskObjErase* thisptr,
+ CObjectInterf* objInterf);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CTaskObjEraseApi
+
+} // namespace editor
+
+} // namespace game
+
+#endif // TASKOBJERASE_H
diff --git a/mss32/include/taskobjmove.h b/mss32/include/taskobjmove.h
new file mode 100644
index 00000000..7ba87b27
--- /dev/null
+++ b/mss32/include/taskobjmove.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TASKOBJMOVE_H
+#define TASKOBJMOVE_H
+
+#include "taskobj.h"
+
+namespace game {
+
+namespace editor {
+
+struct CObjMoveSelect;
+
+struct CTaskObjMoveData
+{
+ CObjMoveSelect* objMoveSelect;
+ int unknown;
+ int unknown2;
+ char unknown3[32];
+};
+
+assert_size(CTaskObjMoveData, 44);
+
+/** Moves existing objects on scenario map. */
+struct CTaskObjMove : public CTaskObj
+{
+ CTaskObjMoveData* moveData;
+};
+
+assert_size(CTaskObjMove, 32);
+
+namespace CTaskObjMoveApi {
+
+struct Api
+{
+ using Constructor = CTaskObjMove*(__thiscall*)(CTaskObjMove* thisptr, CObjectInterf* objInterf);
+ Constructor constructor;
+};
+
+Api& get();
+
+} // namespace CTaskObjMoveApi
+
+} // namespace editor
+
+} // namespace game
+
+#endif // TASKOBJMOVE_H
diff --git a/mss32/include/taskobjprop.h b/mss32/include/taskobjprop.h
new file mode 100644
index 00000000..cb8ed156
--- /dev/null
+++ b/mss32/include/taskobjprop.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TASKOBJPROP_H
+#define TASKOBJPROP_H
+
+#include "taskobj.h"
+
+namespace game {
+
+struct CInterface;
+
+namespace editor {
+
+/** Shows scenario map objects properties. */
+struct CTaskObjProp : public CTaskObj
+{
+ CInterface* propertiesInterface;
+};
+
+assert_size(CTaskObjProp, 32);
+
+struct CTaskObjPropVftable : public CTaskObjVftable
+{
+ using ClosePropertiesInterface = void(__thiscall*)(CTaskObjProp* thisptr);
+ ClosePropertiesInterface closePropertiesInterface;
+};
+
+assert_vftable_size(CTaskObjPropVftable, 12);
+
+namespace CTaskObjPropApi {
+
+struct Api
+{
+ using Constructor = CTaskObjProp*(__thiscall*)(CTaskObjProp* thisptr, CObjectInterf* objInterf);
+ Constructor constructor;
+
+ CTaskObjVftable::DoAction doAction;
+};
+
+Api& get();
+
+} // namespace CTaskObjPropApi
+
+} // namespace editor
+
+} // namespace game
+
+#endif // TASKOBJPROP_H
diff --git a/mss32/include/taskobjprophooks.h b/mss32/include/taskobjprophooks.h
new file mode 100644
index 00000000..a954a0cd
--- /dev/null
+++ b/mss32/include/taskobjprophooks.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TASKOBJPROPHOOKS_H
+#define TASKOBJPROPHOOKS_H
+
+namespace game {
+struct CMqPoint;
+
+namespace editor {
+struct CTaskObjProp;
+}
+} // namespace game
+
+namespace hooks {
+
+bool __fastcall taskObjPropDoActionHooked(game::editor::CTaskObjProp* thisptr,
+ int /*%edx*/,
+ const game::CMqPoint* mapPosition);
+
+}
+
+#endif // TASKOBJPROPHOOKS_H
diff --git a/mss32/include/testcondition.h b/mss32/include/testcondition.h
index d452b11a..23274f40 100644
--- a/mss32/include/testcondition.h
+++ b/mss32/include/testcondition.h
@@ -59,6 +59,24 @@ struct Api
bool samePlayer,
const CMidgardID* triggererStackId);
Create create;
+
+ ITestConditionVftable::Test testFrequency;
+ ITestConditionVftable::Test testLocation;
+ ITestConditionVftable::Test testEnterCity;
+ ITestConditionVftable::Test testOwnCity;
+ ITestConditionVftable::Test testKillStack;
+ ITestConditionVftable::Test testOwnItem;
+ ITestConditionVftable::Test testLeaderOwnItem;
+ ITestConditionVftable::Test testDiplomacy;
+ ITestConditionVftable::Test testAlliance;
+ ITestConditionVftable::Test testLootRuin;
+ ITestConditionVftable::Test testTransformLand;
+ ITestConditionVftable::Test testVisitSite;
+ ITestConditionVftable::Test testLeaderToZone;
+ ITestConditionVftable::Test testLeaderToCity;
+ ITestConditionVftable::Test testItemToLocation;
+ ITestConditionVftable::Test testStackExists;
+ ITestConditionVftable::Test testVarInRange;
};
Api& get();
diff --git a/mss32/include/testkillstack.h b/mss32/include/testkillstack.h
new file mode 100644
index 00000000..4b0a11e2
--- /dev/null
+++ b/mss32/include/testkillstack.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTKILLSTACK_H
+#define TESTKILLSTACK_H
+
+#include "testcondition.h"
+
+namespace game {
+
+struct CMidCondKillStack;
+
+struct CTestKillStack : ITestCondition
+{
+ CMidCondKillStack* condKillStack;
+};
+
+assert_size(CTestKillStack, 8);
+
+} // namespace game
+
+#endif // TESTKILLSTACK_H
diff --git a/mss32/include/testkillstackhooks.h b/mss32/include/testkillstackhooks.h
new file mode 100644
index 00000000..5b3ae6f1
--- /dev/null
+++ b/mss32/include/testkillstackhooks.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTKILLSTACKHOOKS_H
+#define TESTKILLSTACKHOOKS_H
+
+namespace game {
+struct CTestKillStack;
+struct IMidgardObjectMap;
+struct CMidgardID;
+} // namespace game
+
+namespace hooks {
+
+bool __fastcall testKillStackHooked(const game::CTestKillStack* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+}
+
+#endif // TESTKILLSTACKHOOKS_H
diff --git a/mss32/include/testleaderownitem.h b/mss32/include/testleaderownitem.h
new file mode 100644
index 00000000..274bfb7a
--- /dev/null
+++ b/mss32/include/testleaderownitem.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTLEADEROWNITEM_H
+#define TESTLEADEROWNITEM_H
+
+#include "testcondition.h"
+
+namespace game {
+
+struct CMidCondLeaderOwnItem;
+
+struct CTestLeaderOwnItem : public ITestCondition
+{
+ CMidCondLeaderOwnItem* condLeaderOwnItem;
+};
+
+assert_size(CTestLeaderOwnItem, 8);
+
+} // namespace game
+
+#endif // TESTLEADEROWNITEM_H
diff --git a/mss32/include/testleaderownitemhooks.h b/mss32/include/testleaderownitemhooks.h
new file mode 100644
index 00000000..38ecc9c7
--- /dev/null
+++ b/mss32/include/testleaderownitemhooks.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTLEADEROWNITEMHOOKS_H
+#define TESTLEADEROWNITEMHOOKS_H
+
+namespace game {
+struct CTestLeaderOwnItem;
+struct IMidgardObjectMap;
+struct CMidgardID;
+} // namespace game
+
+namespace hooks {
+
+bool __fastcall testLeaderOwnItemHooked(const game::CTestLeaderOwnItem* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+}
+
+#endif // TESTLEADEROWNITEMHOOKS_H
diff --git a/mss32/include/testleadertozone.h b/mss32/include/testleadertozone.h
new file mode 100644
index 00000000..483734ba
--- /dev/null
+++ b/mss32/include/testleadertozone.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTLEADERTOZONE_H
+#define TESTLEADERTOZONE_H
+
+#include "midgardid.h"
+#include "testcondition.h"
+
+namespace game {
+
+struct CMidCondLeaderToZone;
+
+struct CTestLeaderToZone : public ITestCondition
+{
+ CMidCondLeaderToZone* condLeaderToZone;
+ CMidgardID stackId;
+};
+
+assert_size(CTestLeaderToZone, 12);
+
+} // namespace game
+
+#endif // TESTLEADERTOZONE_H
diff --git a/mss32/include/testleadertozonehooks.h b/mss32/include/testleadertozonehooks.h
new file mode 100644
index 00000000..465c480a
--- /dev/null
+++ b/mss32/include/testleadertozonehooks.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTLEADERTOZONEHOOKS_H
+#define TESTLEADERTOZONEHOOKS_H
+
+namespace game {
+struct CTestLeaderToZone;
+struct IMidgardObjectMap;
+struct CMidgardID;
+} // namespace game
+
+namespace hooks {
+
+bool __fastcall testLeaderToZoneHooked(const game::CTestLeaderToZone* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+}
+
+#endif // TESTLEADERTOZONEHOOKS_H
diff --git a/mss32/include/testownitem.h b/mss32/include/testownitem.h
new file mode 100644
index 00000000..c547ebc1
--- /dev/null
+++ b/mss32/include/testownitem.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTOWNITEM_H
+#define TESTOWNITEM_H
+
+#include "testcondition.h"
+
+namespace game {
+
+struct CMidCondOwnItem;
+
+struct CTestOwnItem : public ITestCondition
+{
+ CMidCondOwnItem* condOwnItem;
+};
+
+assert_size(CTestOwnItem, 8);
+
+} // namespace game
+
+#endif // TESTOWNITEM_H
diff --git a/mss32/include/testownitemhooks.h b/mss32/include/testownitemhooks.h
new file mode 100644
index 00000000..72c4a868
--- /dev/null
+++ b/mss32/include/testownitemhooks.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTOWNITEMHOOKS_H
+#define TESTOWNITEMHOOKS_H
+
+namespace game {
+struct CTestOwnItem;
+struct IMidgardObjectMap;
+struct CMidgardID;
+} // namespace game
+
+namespace hooks {
+
+bool __fastcall testOwnItemHooked(const game::CTestOwnItem* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+}
+#endif // TESTOWNITEMHOOKS_H
diff --git a/mss32/include/teststackexists.h b/mss32/include/teststackexists.h
new file mode 100644
index 00000000..3458a71c
--- /dev/null
+++ b/mss32/include/teststackexists.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTSTACKEXISTS_H
+#define TESTSTACKEXISTS_H
+
+#include "testcondition.h"
+
+namespace game {
+
+struct CMidCondStackExists;
+
+struct CTestStackExists : public ITestCondition
+{
+ CMidCondStackExists* condStackExists;
+};
+
+assert_size(CTestStackExists, 8);
+
+} // namespace game
+
+#endif // TESTSTACKEXISTS_H
diff --git a/mss32/include/teststackexistshooks.h b/mss32/include/teststackexistshooks.h
new file mode 100644
index 00000000..7acabb59
--- /dev/null
+++ b/mss32/include/teststackexistshooks.h
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TESTSTACKEXISTSHOOKS_H
+#define TESTSTACKEXISTSHOOKS_H
+
+namespace game {
+struct CTestStackExists;
+struct IMidgardObjectMap;
+struct CMidgardID;
+} // namespace game
+
+namespace hooks {
+
+bool __fastcall testStackExistsHooked(const game::CTestStackExists* thisptr,
+ int /*%edx*/,
+ const game::IMidgardObjectMap* objectMap,
+ const game::CMidgardID* playerId,
+ const game::CMidgardID* eventId);
+
+}
+
+#endif // TESTSTACKEXISTSHOOKS_H
diff --git a/mss32/include/textids.h b/mss32/include/textids.h
index d2b8a0bc..bf447d2c 100644
--- a/mss32/include/textids.h
+++ b/mss32/include/textids.h
@@ -118,6 +118,19 @@ struct TextIds
std::string generationError;
std::string limitExceeded;
} rsg;
+
+ struct ResourceMarket
+ {
+ std::string encyDesc;
+ std::string infiniteAmount;
+ std::string exchangeDesc;
+ std::string exchangeNotAvailable;
+ } resourceMarket;
+
+ struct NobleActions
+ {
+ std::string stealMarketSuccess;
+ } nobleActions;
};
const TextIds& textIds();
diff --git a/mss32/include/timer.h b/mss32/include/timer.h
new file mode 100644
index 00000000..83af59b7
--- /dev/null
+++ b/mss32/include/timer.h
@@ -0,0 +1,84 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TIMER_H
+#define TIMER_H
+
+#include "log.h"
+#include
+#include
+#include
+
+namespace hooks {
+
+// #define D2_MEASURE_EVENTS_TIME
+
+/** Measures scope execution time and writes it to the log file. */
+class ScopedTimer
+{
+ using Clock = std::chrono::high_resolution_clock;
+
+public:
+ ScopedTimer(std::string_view description, std::string_view log)
+ : description{description}
+ , log{log}
+ , start{Clock::now()}
+ { }
+
+ ~ScopedTimer()
+ {
+ const auto elapsed{Clock::now() - start};
+ const auto us{std::chrono::duration_cast(elapsed).count()};
+ logDebug(log, fmt::format("{:s} time {:d} us", description, us));
+ }
+
+private:
+ const std::string_view description;
+ const std::string_view log;
+ const Clock::time_point start;
+};
+
+/** Measures scope execution time and accumulates it using provided reference. */
+class ScopedValueTimer
+{
+ using Clock = std::chrono::high_resolution_clock;
+ using Duration = std::chrono::microseconds;
+ using Type = Duration::rep;
+
+public:
+ ScopedValueTimer(Type& value)
+ : value{value}
+ , start{Clock::now()}
+ { }
+
+ ~ScopedValueTimer()
+ {
+ const auto elapsed{Clock::now() - start};
+ const auto us{std::chrono::duration_cast(elapsed).count()};
+ value += us;
+ }
+
+private:
+ Type& value;
+ const Clock::time_point start;
+};
+
+} // namespace hooks
+
+#endif // TIMER_H
diff --git a/mss32/include/togglebutton.h b/mss32/include/togglebutton.h
index 23f066c4..c074cbc2 100644
--- a/mss32/include/togglebutton.h
+++ b/mss32/include/togglebutton.h
@@ -20,6 +20,7 @@
#ifndef TOGGLEBUTTON_H
#define TOGGLEBUTTON_H
+#include "functordispatch2.h"
#include "interface.h"
#include "smartptr.h"
@@ -27,13 +28,26 @@ namespace game {
struct CToggleButtonVftable;
struct CDialogInterf;
+struct CToggleButton;
+struct IMqImage2;
+
+enum class ToggleButtonState : int
+{
+ Normal,
+ Hovered,
+ Clicked,
+ NormalChecked,
+ HoveredChecked,
+ ClickedChecked,
+ Disabled,
+};
struct CToggleButtonData
{
int buttonChildIndex;
bool checked;
char padding[3];
- SmartPointer ptr;
+ SmartPtr> onClickedFunctor;
SmartPointer ptrArray[7];
};
@@ -52,13 +66,23 @@ assert_size(CToggleButton, 12);
struct CToggleButtonVftable : public CInterfaceVftable
{
- void* method34;
+ /** Sets image for specified toggle button state. */
+ using SetImage = void(__thiscall*)(CToggleButton* thisptr,
+ IMqImage2* image,
+ ToggleButtonState state);
+ SetImage setImage;
+ /** Enables or disables toggle button. */
using SetEnabled = void(__thiscall*)(CToggleButton* thisptr, bool value);
SetEnabled setEnabled;
- void* method36;
- void* method37;
+ /** Returns true if toggle button is enabled. */
+ using IsEnabled = bool(__thiscall*)(const CToggleButton* thisptr);
+ IsEnabled isEnabled;
+
+ /** Calls onClickedFunctor callback if previously set. */
+ using CallOnClicked = void(__thiscall*)(CToggleButton* thisptr);
+ CallOnClicked callOnClicked;
};
assert_vftable_size(CToggleButtonVftable, 38);
diff --git a/mss32/include/trainingcampinterf.h b/mss32/include/trainingcampinterf.h
new file mode 100644
index 00000000..70da12fe
--- /dev/null
+++ b/mss32/include/trainingcampinterf.h
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef TRAININGCAMPINTERF_H
+#define TRAININGCAMPINTERF_H
+
+#include "draganddropinterf.h"
+#include "intvector.h"
+
+namespace game {
+
+struct CMidSiteTrainer;
+
+namespace editor {
+
+struct CTaskObjProp;
+
+struct CTrainingCampInterfData
+{
+ CTaskObjProp* taskObjProp;
+ CMidSiteTrainer* trainingCamp;
+ char unknown2[20];
+ IntVector siteIndices;
+ int selectedImgIsoIndex;
+};
+
+assert_size(CTrainingCampInterfData, 48);
+
+/** Represents DLG_TRAINING_CAMP from ScenEdit.dlg */
+struct CTrainingCampInterf : public CDragAndDropInterf
+{
+ CTrainingCampInterfData* campData;
+};
+
+assert_size(CTrainingCampInterf, 28);
+
+namespace CTrainingCampInterfApi {
+
+struct Api
+{
+ struct ButtonCallback
+ {
+ using Callback = void(__thiscall*)(CTrainingCampInterf* thisptr);
+
+ Callback callback;
+ int unknown;
+ };
+
+ using CreateButtonFunctor = SmartPointer*(__stdcall*)(SmartPointer* functor,
+ int dummy,
+ CTrainingCampInterf* interf,
+ ButtonCallback* callback);
+ CreateButtonFunctor createButtonFunctor;
+};
+
+Api& get();
+
+} // namespace CTrainingCampInterfApi
+
+}
+
+}
+
+#endif // TRAININGCAMPINTERF_H
diff --git a/mss32/include/utils.h b/mss32/include/utils.h
index 92cb6c2e..bf97fc92 100644
--- a/mss32/include/utils.h
+++ b/mss32/include/utils.h
@@ -32,6 +32,7 @@ struct CMidMsgBoxButtonHandler;
struct IMidgardObjectMap;
struct UiEvent;
struct CInterface;
+enum class SoundEffect : int;
} // namespace game
namespace hooks {
@@ -56,6 +57,9 @@ const std::filesystem::path& templatesFolder();
/** Returns full path to the exports folder. */
const std::filesystem::path& exportsFolder();
+/** Returns full path to the ScenData folder. */
+const std::filesystem::path& scenDataFolder();
+
/** Returns full path to the executable that is currently running. */
const std::filesystem::path& exePath();
@@ -117,10 +121,12 @@ std::uint32_t createMessageEvent(game::UiEvent* messageEvent,
bool computeHash(const std::filesystem::path& folder, std::string& hash);
/** Executes function for each scenario object with specified id type. */
-void forEachScenarioObject(game::IMidgardObjectMap* objectMap,
+void forEachScenarioObject(const game::IMidgardObjectMap* objectMap,
game::IdType idType,
const std::function& func);
+void playSoundEffect(game::SoundEffect effect);
+
template
static inline void replaceRttiInfo(game::RttiInfo& dst, const T* src, bool copyVftable = true)
{
diff --git a/mss32/include/visitorcreatesite.h b/mss32/include/visitorcreatesite.h
new file mode 100644
index 00000000..27ee118c
--- /dev/null
+++ b/mss32/include/visitorcreatesite.h
@@ -0,0 +1,63 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef VISITORCREATESITE_H
+#define VISITORCREATESITE_H
+
+#include "d2string.h"
+#include "mqpoint.h"
+#include "sitecategories.h"
+#include "visitors.h"
+
+namespace game {
+
+namespace editor {
+
+struct CVisitorCreateSite : public CScenarioVisitor
+{
+ LSiteCategory category;
+ CMqPoint position;
+ int imgIso;
+ String interfaceImage;
+ String name;
+ String description;
+};
+
+assert_size(CVisitorCreateSite, 80);
+
+namespace CVisitorCreateSiteApi {
+
+struct Api
+{
+ using CanApply = bool(__thiscall*)(const CVisitorCreateSite* thisptr);
+ CanApply canApply;
+
+ using Apply = bool(__thiscall*)(const CVisitorCreateSite* thisptr);
+ Apply apply;
+};
+
+Api& get();
+
+} // namespace CVisitorCreateSiteApi
+
+} // namespace editor
+
+} // namespace game
+
+#endif // VISITORCREATESITE_H
diff --git a/mss32/include/visitorcreatesitehooks.h b/mss32/include/visitorcreatesitehooks.h
new file mode 100644
index 00000000..e1465316
--- /dev/null
+++ b/mss32/include/visitorcreatesitehooks.h
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef VISITORCREATESITEHOOKS_H
+#define VISITORCREATESITEHOOKS_H
+
+namespace game::editor {
+struct CVisitorCreateSite;
+
+}
+
+namespace hooks {
+
+bool __fastcall visitorCreateSiteCanApplyHooked(const game::editor::CVisitorCreateSite* thisptr,
+ int /* %edx */);
+
+bool __fastcall visitorCreateSiteApplyHooked(const game::editor::CVisitorCreateSite* thisptr,
+ int /* %edx */);
+
+} // namespace hooks
+
+#endif // VISITORCREATESITEHOOKS_H
diff --git a/mss32/include/visitors.h b/mss32/include/visitors.h
index 3c389632..d01b9173 100644
--- a/mss32/include/visitors.h
+++ b/mss32/include/visitors.h
@@ -25,6 +25,10 @@ namespace game {
struct CMidgardID;
struct IMidgardObjectMap;
struct CScenarioVisitorVftable;
+struct LAttitudesCategory;
+struct LItemCategory;
+struct LSiteCategory;
+struct CMqPoint;
/**
* Base for all visitor classes.
@@ -188,6 +192,123 @@ struct Api
IMidgardObjectMap* objectMap,
int apply);
ExtractUnitFromGroup extractUnitFromGroup;
+
+ /**
+ * Changes player attitude.
+ * Uses CVisitorPlayerSetAttitude.
+ * Can be used only in Scenario Editor.
+ * @param[in] playerId id of player to change.
+ * @param[in] attitude new attitude category to set.
+ * @param[in] objectMap interface used for objects search.
+ * @param apply specifies whether attitude should be changed.
+ * @returns true if player attitude was changed when apply set to 1. If apply set to 0, returns
+ * whether visitor can be applied.
+ */
+ using PlayerSetAttitude = bool(__stdcall*)(const CMidgardID* playerId,
+ const LAttitudesCategory* attitude,
+ IMidgardObjectMap* objectMap,
+ int apply);
+ PlayerSetAttitude playerSetAttitude;
+
+ /**
+ * Sets source template id for a stack.
+ * Uses CVisitorSetStackSrcTemplate.
+ * @param[in] stackId id of stack to change.
+ * @param[in] stackTemplateId source template id to set.
+ * @param[in] objectMap interface used for objects search.
+ * @param apply specifies whether stack source template should be changed.
+ * @returns true if source template was set when apply set to 1. If apply set to 0, returns
+ * whether visitor can be applied.
+ */
+ using SetStackSrcTemplate = bool(__stdcall*)(const CMidgardID* stackId,
+ const CMidgardID* stackTemplateId,
+ IMidgardObjectMap* objectMap,
+ int apply);
+ SetStackSrcTemplate setStackSrcTemplate;
+
+ /**
+ * Allows merchant to buy items of specified category.
+ * Can be called only in Scenario Editor.
+ * Uses CVisitorMerchantAddBuyCategory.
+ * @param[in] siteId id of merchant to change.
+ * @param[in] itemCategory category of items merchant is allowed to buy.
+ * @param[in] objectMap interface used for objects search.
+ * @param apply specifies whether merchant object should be changed.
+ * @returns true if merchant was changed successfully when apply set to 1. If ally set to 0,
+ * returns whether visitor can be applied.
+ */
+ using MerchantAddBuyCategory = bool(__stdcall*)(const CMidgardID* siteId,
+ const LItemCategory* itemCategory,
+ IMidgardObjectMap* objectMap,
+ int apply);
+ MerchantAddBuyCategory merchantAddBuyCategory;
+
+ /**
+ * Creates site of specified category.
+ * Can be called only in Scenario Editor.
+ * Uses CVisitorCreateSite.
+ * @param[in] site site category to create.
+ * @param[in] mapPosition position on a map where to create a new site.
+ * @param imgIso site scenario map image index.
+ * @param[in] imgIntf site interface image.
+ * @param[in] name site name.
+ * @param[in] description site description.
+ * @param[in] objectMap interface used for objects search.
+ * @param apply specifies whether site object should be created.
+ * @returns true if site was creates successfully when apply set to 1. If apply set to 0,
+ * returns wheter visitor can be applied.
+ */
+ using CreateSite = bool(__stdcall*)(const LSiteCategory* site,
+ const CMqPoint* mapPosition,
+ int imgIso,
+ const char* imgIntf,
+ const char* name,
+ const char* description,
+ IMidgardObjectMap* objectMap,
+ int apply);
+ CreateSite createSite;
+
+ /**
+ * Changes site object information.
+ * Can be called only in Scenario Editor.
+ * Uses CVisitorChangeSiteInfo.
+ * @param[in] siteId id of existing site object to change.
+ * @param[in] name new name for a site.
+ * @param[in] description new description for a site.
+ * @param[in] objectMap interface used for objects search.
+ * @param apply specifies whether site object info should be changed.
+ * @returns true if site info was changed successfully when apply set to 1.
+ * If apply set to 0, returns wheter visitor can be applied.
+ */
+ using ChangeSiteInfo = bool(__stdcall*)(const CMidgardID* siteId,
+ const char* name,
+ const char* description,
+ IMidgardObjectMap* objectMap,
+ int apply);
+ ChangeSiteInfo changeSiteInfo;
+
+ /**
+ * Changes site object image on a strategic map.
+ * Can be called only in Scenario Editor.
+ * Uses CVisitorChangeSiteImage.
+ * @param[in] siteId id of existing site object to change.
+ * @param imgIso strategic map image index.
+ * @param[in] objectMap interface used for objects search.
+ * @param apply specifies whether site object image should be changed.
+ * @returns true if site image was changed successfully when apply set to 1.
+ * If apply set to 0, returns whether visitor can be applied.
+ */
+ using ChangeSiteImage = bool(__stdcall*)(const CMidgardID* siteId,
+ int imgIso,
+ IMidgardObjectMap* objectMap,
+ int apply);
+ ChangeSiteImage changeSiteImage;
+
+ using ChangeSiteAiPriority = bool(__stdcall*)(const CMidgardID* siteId,
+ int aiPriority,
+ IMidgardObjectMap* objectMap,
+ int apply);
+ ChangeSiteAiPriority changeSiteAiPriority;
};
Api& get();
diff --git a/mss32/include/widgetinterf.h b/mss32/include/widgetinterf.h
index 47c68f73..0c7e3671 100644
--- a/mss32/include/widgetinterf.h
+++ b/mss32/include/widgetinterf.h
@@ -26,6 +26,7 @@
#include "functordispatch3.h"
#include "functordispatch4.h"
#include "interface.h"
+#include "mqpoint.h"
#include "uievent.h"
namespace game {
diff --git a/mss32/midgardid.natvis b/mss32/midgardid.natvis
new file mode 100644
index 00000000..3cf799f6
--- /dev/null
+++ b/mss32/midgardid.natvis
@@ -0,0 +1,76 @@
+
+
+
+
+ {{ value={(unsigned int)value,X} }}
+
+ - "Global (G)"
+ - "Campaign (C)"
+ - "Scenario (S)"
+ - "External (X)"
+ - ((unsigned int)value >> 22) & 0xff
+ - "Empty (00)"
+ - "App text (TA)"
+ - "Building (BB)"
+ - "Race (RR)"
+ - "Lord (LR)"
+ - "Spell (SS)"
+ - "Unit global (UU)"
+ - "Unit generated (UG)"
+ - "Unit modifier (UM)"
+ - "Attack (AA)"
+ - "Text global (TG)"
+ - "Landmark global (MG)"
+ - "Item global (IG)"
+ - "Noble action (NA)"
+ - "Dynamic upgrade (DU)"
+ - "Dynamic attack (DA)"
+ - "Dynamic alt. attack (AL)"
+ - "Dynamic attack 2 (DC)"
+ - "Dynamic alt. attack 2 (AC)"
+ - "Campaign file (CC)"
+ - "CW"
+ - "CO"
+ - "Plan (PN)"
+ - "Object count (OB)"
+ - "Scenario file (SC)"
+ - "Map (MP)"
+ - "Map block (MB)"
+ - "Scenario info (IF)"
+ - "Spell effects (ET)"
+ - "Fortification (FT)"
+ - "Player (PL)"
+ - "Player known spells (KS)"
+ - "Fog (FG)"
+ - "Player buildings (PB)"
+ - "Road (RA)"
+ - "Stack (KC)"
+ - "Unit (UU)"
+ - "Landmark (MM)"
+ - "Item (IM)"
+ - "Bag (BG)"
+ - "Site (SI)"
+ - "Ruin (RU)"
+ - "Tomb (TB)"
+ - "Rod (RD)"
+ - "Crystal (CR)"
+ - "Diplomacy (DP)"
+ - "Spell cast (ST)"
+ - "Location (LO)"
+ - "Stack template (TM)"
+ - "Event (EV)"
+ - "Stack destroyed (SD)"
+ - "Talisman charges (TC)"
+ - "MT"
+ - "Mountains (ML)"
+ - "Subrace (SR)"
+ - "Subrace type (BR)"
+ - "Quest log (QL)"
+ - "Turn summary (TS)"
+ - "Scenario variables (SV)"
+ - "Invalid"
+ - (unsigned int)value & 0xffff
+
+
+
+
\ No newline at end of file
diff --git a/mss32/mss32.vcxproj b/mss32/mss32.vcxproj
index 92f429ca..4eb1aef0 100644
--- a/mss32/mss32.vcxproj
+++ b/mss32/mss32.vcxproj
@@ -155,6 +155,8 @@ call ..\buildDetours.bat ..\Detours
+
+
@@ -195,9 +197,20 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
+
+
+
+
@@ -227,10 +240,17 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
@@ -238,12 +258,16 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
@@ -291,10 +315,15 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
@@ -303,15 +332,34 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -454,19 +502,27 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
+
@@ -478,17 +534,30 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -516,6 +585,8 @@ call ..\buildDetours.bat ..\Detours
+
+
@@ -568,6 +639,8 @@ call ..\buildDetours.bat ..\Detours
+
+
@@ -637,9 +710,20 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
+
+
+
+
@@ -670,13 +754,25 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
+
+
+
+
+
@@ -689,6 +785,7 @@ call ..\buildDetours.bat ..\Detours
+
@@ -752,6 +849,7 @@ call ..\buildDetours.bat ..\Detours
+
@@ -773,18 +871,34 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -794,6 +908,8 @@ call ..\buildDetours.bat ..\Detours
+
+
@@ -1093,12 +1209,17 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
@@ -1109,9 +1230,32 @@ call ..\buildDetours.bat ..\Detours
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1206,6 +1350,11 @@ call ..\buildDetours.bat ..\Detours
+
+
+ false
+
+
diff --git a/mss32/mss32.vcxproj.filters b/mss32/mss32.vcxproj.filters
index fa589d57..46d3fff2 100644
--- a/mss32/mss32.vcxproj.filters
+++ b/mss32/mss32.vcxproj.filters
@@ -50,6 +50,12 @@
{54ad4f5d-bf7a-487f-9a3c-94547ca33f44}
+
+ {4414afc9-2087-4905-bf4d-220042197dd5}
+
+
+ {6454c2cc-1767-4c10-9a0a-dee340ea6ca0}
+
@@ -1265,12 +1271,225 @@
bindings
+
+ bindings
+
+
+ bindings
+
+
+ bindings
+
game
game
+
+ bindings
+
+
+ game
+
+
+ bindings
+
+
+ bindings
+
+
+ bindings
+
+
+ bindings
+
+
+ bindings
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ features
+
+
+ hooks
+
+
+ hooks
+
+
+ hooks
+
+
+ features
+
+
+ hooks
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ hooks
+
+
+ hooks
+
+
+ hooks
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ hooks
+
+
+ features\custom scenario objects
+
+
+ features\custom scenario objects
+
+
+ features\custom scenario objects
+
+
+ features\custom scenario objects
+
+
+ bindings
+
+
+ bindings
+
+
+ game
+
+
+ game
+
+
+ features\custom noble actions
+
+
+ features\custom noble actions
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ features\custom noble actions
+
+
+ features\custom noble actions
+
+
+ features\custom noble actions
+
@@ -3295,6 +3514,15 @@
bindings
+
+ bindings
+
+
+ bindings
+
+
+ bindings
+
game
@@ -3304,6 +3532,216 @@
game
+
+ bindings
+
+
+ game
+
+
+ game
+
+
+ bindings
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ bindings
+
+
+ bindings
+
+
+ bindings
+
+
+ bindings
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ features
+
+
+ game
+
+
+ hooks
+
+
+ utils
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ hooks
+
+
+ features
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ hooks
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ hooks
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ hooks
+
+
+ game
+
+
+ hooks
+
+
+ features\custom scenario objects
+
+
+ features\custom scenario objects
+
+
+ features\custom scenario objects
+
+
+ features\custom scenario objects
+
+
+ bindings
+
+
+ bindings
+
+
+ game
+
+
+ game
+
+
+ features\custom noble actions
+
+
+ features\custom noble actions
+
+
+ game
+
+
+ game
+
+
+ hooks
+
+
+ features\custom noble actions
+
+
+ features\custom noble actions
+
+
+ features\custom noble actions
+
@@ -3313,4 +3751,7 @@
+
+
+
\ No newline at end of file
diff --git a/mss32/src/aiattitudescat.cpp b/mss32/src/aiattitudescat.cpp
new file mode 100644
index 00000000..52663a9a
--- /dev/null
+++ b/mss32/src/aiattitudescat.cpp
@@ -0,0 +1,113 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "aiattitudescat.h"
+#include "version.h"
+#include
+
+namespace game {
+namespace AttitudeCategories {
+
+// clang-format off
+static std::array categories = {{
+ // Akella
+ Categories{
+ (LAttitudesCategory*)0x83a0f0,
+ (LAttitudesCategory*)0x83a0c8,
+ (LAttitudesCategory*)0x83a0b8,
+ (LAttitudesCategory*)0x83a0d8,
+ },
+ // Russobit
+ Categories{
+ (LAttitudesCategory*)0x83a0f0,
+ (LAttitudesCategory*)0x83a0c8,
+ (LAttitudesCategory*)0x83a0b8,
+ (LAttitudesCategory*)0x83a0d8,
+ },
+ // Gog
+ Categories{
+ (LAttitudesCategory*)0x8380a0,
+ (LAttitudesCategory*)0x838078,
+ (LAttitudesCategory*)0x838068,
+ (LAttitudesCategory*)0x838088,
+ },
+ // Scenario Editor
+ Categories{
+ (LAttitudesCategory*)0x665c28,
+ (LAttitudesCategory*)0x665c00,
+ (LAttitudesCategory*)0x665bf0,
+ (LAttitudesCategory*)0x665c10,
+ }
+}};
+// clang-format on
+
+Categories& get()
+{
+ return categories[static_cast(hooks::gameVersion())];
+}
+
+} // namespace AttitudeCategories
+
+namespace LAttitudesCategoryTableApi {
+
+// clang-format off
+static std::array functions = {{
+ // Akella
+ Api{
+ (Api::Constructor)0x591c13,
+ (Api::Init)0x591d7a,
+ (Api::ReadCategory)0x591df2,
+ (Api::InitDone)0x591d35,
+ (Api::FindCategoryById)0x590da2,
+ },
+ // Russobit
+ Api{
+ (Api::Constructor)0x591c13,
+ (Api::Init)0x591d7a,
+ (Api::ReadCategory)0x591df2,
+ (Api::InitDone)0x591d35,
+ (Api::FindCategoryById)0x590da2,
+ },
+ // Gog
+ Api{
+ (Api::Constructor)0x590d2b,
+ (Api::Init)0x590e92,
+ (Api::ReadCategory)0x590f0a,
+ (Api::InitDone)0x590e4d,
+ (Api::FindCategoryById)0x58feba,
+ },
+ // Scenario Editor
+ Api{
+ (Api::Constructor)0x53d86e,
+ (Api::Init)0x53d9d5,
+ (Api::ReadCategory)0x53da4d,
+ (Api::InitDone)0x53d990,
+ (Api::FindCategoryById)0x4440e5,
+ }
+}};
+// clang-format on
+
+Api& get()
+{
+ return functions[static_cast(hooks::gameVersion())];
+}
+
+} // namespace LAttitudesCategoryTableApi
+
+} // namespace game
diff --git a/mss32/src/aiattitudestable.cpp b/mss32/src/aiattitudestable.cpp
new file mode 100644
index 00000000..57d8b5fb
--- /dev/null
+++ b/mss32/src/aiattitudestable.cpp
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "aiattitudestable.h"
+#include "version.h"
+#include
+
+namespace game::CAiAttitudesTableApi {
+
+// clang-format off
+static std::array functions = {{
+ // Akella
+ Api{
+ (Api::Find)0
+ },
+ // Russobit
+ Api{
+ (Api::Find)0
+ },
+ // Gog
+ Api{
+ (Api::Find)0
+ },
+ // Scenario Editor
+ Api{
+ (Api::Find)0x534880,
+ }
+}};
+// clang-format on
+
+Api& get()
+{
+ return functions[static_cast(hooks::gameVersion())];
+}
+
+} // namespace game::CAiAttitudesTableApi
diff --git a/mss32/src/attackutils.cpp b/mss32/src/attackutils.cpp
index 97eb7552..926aaafb 100644
--- a/mss32/src/attackutils.cpp
+++ b/mss32/src/attackutils.cpp
@@ -76,7 +76,7 @@ int getBoostDamage(int level)
using namespace game;
const auto& global = GlobalDataApi::get();
- const auto vars = *(*global.getGlobalData())->globalVariables;
+ const auto vars = (*global.getGlobalData())->globalVariables->data;
int count = std::size(vars->battleBoostDamage);
return (0 < level && level <= count) ? vars->battleBoostDamage[level - 1] : 0;
@@ -87,7 +87,7 @@ int getLowerDamage(int level)
using namespace game;
const auto& global = GlobalDataApi::get();
- const auto vars = *(*global.getGlobalData())->globalVariables;
+ const auto vars = (*global.getGlobalData())->globalVariables->data;
int count = std::size(vars->battleLowerDamage);
return (0 < level && level <= count) ? vars->battleLowerDamage[level - 1] : 0;
@@ -101,7 +101,7 @@ int getLowerInitiative(int level)
return 0;
const auto& global = GlobalDataApi::get();
- const auto vars = *(*global.getGlobalData())->globalVariables;
+ const auto vars = (*global.getGlobalData())->globalVariables->data;
return vars->battleLowerIni;
}
@@ -231,4 +231,128 @@ int getAttackMaxTargets(const game::AttackReachId id)
return 0;
}
+const game::LAttackSource* getAttackSourceById(game::AttackSourceId id)
+{
+ using namespace game;
+
+ const auto& attackSources{AttackSourceCategories::get()};
+
+ switch (id) {
+ default: {
+ const auto& customSources = hooks::getCustomAttacks().sources;
+ for (const auto& customSource : customSources) {
+ if (customSource.source.id == id) {
+ return &customSource.source;
+ }
+ }
+
+ // Could not find source id even in custom sources
+ return nullptr;
+ }
+ case AttackSourceId::Weapon:
+ return attackSources.weapon;
+
+ case AttackSourceId::Mind:
+ return attackSources.mind;
+
+ case AttackSourceId::Life:
+ return attackSources.life;
+
+ case AttackSourceId::Death:
+ return attackSources.death;
+
+ case AttackSourceId::Fire:
+ return attackSources.fire;
+
+ case AttackSourceId::Water:
+ return attackSources.water;
+
+ case AttackSourceId::Earth:
+ return attackSources.earth;
+
+ case AttackSourceId::Air:
+ return attackSources.air;
+ }
+}
+
+const game::LAttackClass* getAttackClassById(game::AttackClassId id)
+{
+ using namespace game;
+
+ const auto& attackClasses{AttackClassCategories::get()};
+
+ switch (id) {
+ case AttackClassId::Damage:
+ return attackClasses.damage;
+
+ case AttackClassId::Drain:
+ return attackClasses.drain;
+
+ case AttackClassId::Paralyze:
+ return attackClasses.paralyze;
+
+ case AttackClassId::Heal:
+ return attackClasses.heal;
+
+ case AttackClassId::Fear:
+ return attackClasses.fear;
+
+ case AttackClassId::BoostDamage:
+ return attackClasses.boostDamage;
+
+ case AttackClassId::Petrify:
+ return attackClasses.petrify;
+
+ case AttackClassId::LowerDamage:
+ return attackClasses.lowerDamage;
+
+ case AttackClassId::LowerInitiative:
+ return attackClasses.lowerInitiative;
+
+ case AttackClassId::Poison:
+ return attackClasses.poison;
+
+ case AttackClassId::Frostbite:
+ return attackClasses.frostbite;
+
+ case AttackClassId::Revive:
+ return attackClasses.revive;
+
+ case AttackClassId::DrainOverflow:
+ return attackClasses.drainOverflow;
+
+ case AttackClassId::Cure:
+ return attackClasses.cure;
+
+ case AttackClassId::Summon:
+ return attackClasses.summon;
+
+ case AttackClassId::DrainLevel:
+ return attackClasses.drainLevel;
+
+ case AttackClassId::GiveAttack:
+ return attackClasses.giveAttack;
+
+ case AttackClassId::Doppelganger:
+ return attackClasses.doppelganger;
+
+ case AttackClassId::TransformSelf:
+ return attackClasses.transformSelf;
+
+ case AttackClassId::TransformOther:
+ return attackClasses.transformOther;
+
+ case AttackClassId::Blister:
+ return attackClasses.blister;
+
+ case AttackClassId::BestowWards:
+ return attackClasses.bestowWards;
+
+ case AttackClassId::Shatter:
+ return attackClasses.shatter;
+ }
+
+ return nullptr;
+}
+
} // namespace hooks
diff --git a/mss32/src/battlemsgdata.cpp b/mss32/src/battlemsgdata.cpp
index a74e0018..a534b19d 100644
--- a/mss32/src/battlemsgdata.cpp
+++ b/mss32/src/battlemsgdata.cpp
@@ -61,6 +61,7 @@ static std::array functions = {{
(Api::FindAttackTargetWithAllReach)0x5d0ee1,
(Api::FindSpecificAttackTarget)0x5d19e9,
(Api::FindSpecificAttackTarget)0x5d3079,
+ (Api::FindSpecificAttackTarget)0x5d130b,
(Api::FindDoppelgangerAttackTarget)0x5d2409,
(Api::FindDamageAttackTargetWithNonAllReach)0x5d0a1b,
(Api::FindDamageAttackTargetWithAnyReach)0x5d0cf1,
@@ -87,6 +88,7 @@ static std::array functions = {{
(Api::FillEmptyTargetsListForAllAnyAttackReach)0x66429c,
(Api::FillTargetsListForAdjacentAttackReach)0x664312,
(Api::IsAutoBattle)0x6243c8,
+ (Api::IsFastBattle)0x6243e1,
(Api::AlliesNotPreventingAdjacentAttack)0x65c19c,
(Api::GiveAttack)0x623ade,
(Api::RemoveFiniteBoostLowerDamage)0x627b07,
@@ -94,6 +96,12 @@ static std::array functions = {{
(Api::UpdateBattleActions)0x625253,
(Api::GetItemAttackTargets)0x62571b,
(Api::BeforeBattleRound)0x623440,
+ (Api::AiChooseBattleAction)0x5CF388,
+ (Api::GetRetreatStatus)0x624f37,
+ (Api::SetRetreatStatus)0x624f77,
+ (Api::IsRetreatDecisionWasMade)0x624f01,
+ (Api::SetRetreatDecisionWasMade)0x624f17,
+ (Api::IsAfterBattle)0x622b4b,
},
// Russobit
Api{
@@ -131,6 +139,7 @@ static std::array functions = {{
(Api::FindAttackTargetWithAllReach)0x5d0ee1,
(Api::FindSpecificAttackTarget)0x5d19e9,
(Api::FindSpecificAttackTarget)0x5d3079,
+ (Api::FindSpecificAttackTarget)0x5d130b,
(Api::FindDoppelgangerAttackTarget)0x5d2409,
(Api::FindDamageAttackTargetWithNonAllReach)0x5d0a1b,
(Api::FindDamageAttackTargetWithAnyReach)0x5d0cf1,
@@ -157,6 +166,7 @@ static std::array functions = {{
(Api::FillEmptyTargetsListForAllAnyAttackReach)0x66429c,
(Api::FillTargetsListForAdjacentAttackReach)0x664312,
(Api::IsAutoBattle)0x6243c8,
+ (Api::IsFastBattle)0x6243e1,
(Api::AlliesNotPreventingAdjacentAttack)0x65c19c,
(Api::GiveAttack)0x623ade,
(Api::RemoveFiniteBoostLowerDamage)0x627b07,
@@ -164,6 +174,12 @@ static std::array functions = {{
(Api::UpdateBattleActions)0x625253,
(Api::GetItemAttackTargets)0x62571b,
(Api::BeforeBattleRound)0x623440,
+ (Api::AiChooseBattleAction)0x5CF388,
+ (Api::GetRetreatStatus)0x624f37,
+ (Api::SetRetreatStatus)0x624f77,
+ (Api::IsRetreatDecisionWasMade)0x624f01,
+ (Api::SetRetreatDecisionWasMade)0x624f17,
+ (Api::IsAfterBattle)0x622b4b,
},
// Gog
Api{
@@ -201,6 +217,7 @@ static std::array functions = {{
(Api::FindAttackTargetWithAllReach)0x5cfe12,
(Api::FindSpecificAttackTarget)0x5d091a,
(Api::FindSpecificAttackTarget)0x5d1faa,
+ (Api::FindSpecificAttackTarget)0x5d023c,
(Api::FindDoppelgangerAttackTarget)0x5d133a,
(Api::FindDamageAttackTargetWithNonAllReach)0x5cf94c,
(Api::FindDamageAttackTargetWithAnyReach)0x5cfc22,
@@ -227,6 +244,7 @@ static std::array functions = {{
(Api::FillEmptyTargetsListForAllAnyAttackReach)0x662d1c,
(Api::FillTargetsListForAdjacentAttackReach)0x662d92,
(Api::IsAutoBattle)0x622f58,
+ (Api::IsFastBattle)0x622f71,
(Api::AlliesNotPreventingAdjacentAttack)0x65ac1c,
(Api::GiveAttack)0x62266e,
(Api::RemoveFiniteBoostLowerDamage)0x626647,
@@ -234,6 +252,12 @@ static std::array functions = {{
(Api::UpdateBattleActions)0x623d93,
(Api::GetItemAttackTargets)0x62425b,
(Api::BeforeBattleRound)0x621fd0,
+ (Api::AiChooseBattleAction)0x5ce2b9,
+ (Api::GetRetreatStatus)0x623ac7,
+ (Api::SetRetreatStatus)0x623b07,
+ (Api::IsRetreatDecisionWasMade)0x623a91,
+ (Api::SetRetreatDecisionWasMade)0x623aa7,
+ (Api::IsAfterBattle)0x6216db,
},
}};
// clang-format on
diff --git a/mss32/src/battlemsgdatahooks.cpp b/mss32/src/battlemsgdatahooks.cpp
index ff651b16..003c9a1f 100644
--- a/mss32/src/battlemsgdatahooks.cpp
+++ b/mss32/src/battlemsgdatahooks.cpp
@@ -18,15 +18,30 @@
*/
#include "battlemsgdatahooks.h"
+#include "attackview.h"
#include "batattack.h"
+#include "bindings/battlemsgdataviewmutable.h"
+#include "bindings/groupview.h"
+#include "bindings/idview.h"
+#include "bindings/unitview.h"
+#include "customaibattle.h"
#include "customattacks.h"
+#include "fortification.h"
#include "gameutils.h"
+#include "groupview.h"
#include "intset.h"
#include "log.h"
+#include "midplayer.h"
#include "midstack.h"
#include "modifierutils.h"
#include "originalfunctions.h"
+#include "racetype.h"
+#include "scripts.h"
+#include "settings.h"
+#include "unitimplview.h"
#include "unitutils.h"
+#include "usunitimpl.h"
+#include "utils.h"
#include
#include
@@ -392,4 +407,158 @@ void __fastcall beforeBattleRoundHooked(game::BattleMsgData* thisptr, int /*%edx
freeTransformSelf.used = false;
}
+void __stdcall aiChooseBattleActionHooked(const game::IMidgardObjectMap* objectMap,
+ game::BattleMsgData* battleMsgData,
+ const game::CMidgardID* unitId,
+ const game::Set* possibleActions,
+ const game::PossibleTargets* possibleTargets,
+ game::BattleAction* battleAction,
+ game::CMidgardID* targetUnitId,
+ game::CMidgardID* attackerUnitId)
+{
+ using namespace game;
+
+ const auto& chooseBattleAction{getOriginalFunctions().aiChooseBattleAction};
+
+ const auto& customBattleLogic{getCustomAiBattleLogic()};
+ if (!customBattleLogic.customBattleLogicEnabled) {
+ // Custom battle action logic disabled, use original
+ chooseBattleAction(objectMap, battleMsgData, unitId, possibleActions, possibleTargets,
+ battleAction, targetUnitId, attackerUnitId);
+ return;
+ }
+
+ CMidgardID allyGroupId{};
+ gameFunctions().getAllyOrEnemyGroupId(&allyGroupId, battleMsgData, unitId, true);
+
+ const CMidPlayer* allyPlayer{getGroupOwner(objectMap, &allyGroupId)};
+ if (!allyPlayer) {
+ const auto message{fmt::format("Could not find player that owns unit {:s} in group {:s}",
+ idToString(unitId), idToString(&allyGroupId))};
+
+ if (userSettings().battle.debugAi) {
+ showErrorMessageBox(message);
+ } else {
+ logError("mssProxyError.log", message);
+ }
+
+ chooseBattleAction(objectMap, battleMsgData, unitId, possibleActions, possibleTargets,
+ battleAction, targetUnitId, attackerUnitId);
+ return;
+ }
+
+ const auto& battleLogicMap{customBattleLogic.attitudeBattleLogic};
+ const auto it{battleLogicMap.find(allyPlayer->attitude.id)};
+ if (it == battleLogicMap.cend()) {
+ const auto message{fmt::format("Could not find battle action script by attitude {:d}",
+ static_cast(allyPlayer->attitude.id))};
+
+ if (userSettings().battle.debugAi) {
+ showErrorMessageBox(message);
+ } else {
+ logError("mssProxyError.log", message);
+ }
+
+ chooseBattleAction(objectMap, battleMsgData, unitId, possibleActions, possibleTargets,
+ battleAction, targetUnitId, attackerUnitId);
+ return;
+ }
+
+ const auto& actionScript{it->second};
+
+ std::optional env;
+ const auto path{scriptsFolder() / actionScript};
+ auto chooseAction = getScriptFunction(path, "chooseAction", env, true, true);
+ if (!chooseAction) {
+ const auto message{
+ fmt::format("Could not find 'chooseAction' function. Script '{:s}'", path.string())};
+
+ if (userSettings().battle.debugAi) {
+ showErrorMessageBox(message);
+ } else {
+ logError("mssProxyError.log", message);
+ }
+
+ chooseBattleAction(objectMap, battleMsgData, unitId, possibleActions, possibleTargets,
+ battleAction, targetUnitId, attackerUnitId);
+ return;
+ }
+
+ try {
+ const bindings::BattleMsgDataViewMutable battleView{battleMsgData, objectMap};
+
+ const CMidUnit* unit = static_cast(
+ objectMap->vftable->findScenarioObjectById(objectMap, unitId));
+ const bindings::UnitView activeUnitView{unit};
+
+ std::vector actions;
+ actions.reserve(possibleActions->length);
+ for (auto& v : *possibleActions) {
+ actions.push_back(v);
+ }
+
+ const CMidgardID* attTargGroupId{possibleTargets->attackTargetGroupId};
+ std::optional attackTargetGroupView;
+ if (const auto* group = getGroup(objectMap, attTargGroupId)) {
+ attackTargetGroupView = bindings::GroupView{group, objectMap, attTargGroupId};
+ }
+
+ std::vector attackTargets;
+ attackTargets.reserve(possibleTargets->attackTargets->length);
+ for (auto& v : *possibleTargets->attackTargets) {
+ attackTargets.push_back(v);
+ }
+
+ const CMidgardID* item1TargGroupId{possibleTargets->item1TargetGroupId};
+ std::optional item1TargetGroupView;
+ if (const auto* group = getGroup(objectMap, item1TargGroupId)) {
+ item1TargetGroupView = bindings::GroupView{group, objectMap, item1TargGroupId};
+ }
+
+ std::vector item1Targets;
+ item1Targets.reserve(possibleTargets->item1Targets->length);
+ for (auto& v : *possibleTargets->item1Targets) {
+ item1Targets.push_back(v);
+ }
+
+ const CMidgardID* item2TargGroupId{possibleTargets->item2TargetGroupId};
+ std::optional item2TargetGroupView;
+ if (const auto* group = getGroup(objectMap, item2TargGroupId)) {
+ item2TargetGroupView = bindings::GroupView{group, objectMap, item2TargGroupId};
+ }
+
+ std::vector item2Targets;
+ item2Targets.reserve(possibleTargets->item2Targets->length);
+ for (auto& v : *possibleTargets->item2Targets) {
+ item2Targets.push_back(v);
+ }
+
+ int chosenAction = 0;
+ bindings::IdView chosenTargetId{(CMidgardID*)nullptr};
+ bindings::IdView chosenAttackerId{(CMidgardID*)nullptr};
+ sol::tie(chosenAction, chosenTargetId,
+ chosenAttackerId) = (*chooseAction)(battleView, activeUnitView, actions,
+ attackTargetGroupView, attackTargets,
+ item1TargetGroupView, item1Targets,
+ item2TargetGroupView, item2Targets);
+
+ *targetUnitId = chosenTargetId.id;
+ *attackerUnitId = chosenAttackerId.id;
+ *battleAction = static_cast(chosenAction);
+ } catch (const std::exception& e) {
+ const auto message{
+ fmt::format("Failed to run '{:s}' script. Reason: {:s}", path.string(), e.what())};
+
+ if (userSettings().battle.debugAi) {
+ showErrorMessageBox(message);
+ } else {
+ logError("mssProxyError.log", message);
+ }
+
+ *targetUnitId = *unitId;
+ *attackerUnitId = *unitId;
+ *battleAction = userSettings().battle.fallbackAction;
+ }
+}
+
} // namespace hooks
diff --git a/mss32/src/bindings/battlemsgdataview.cpp b/mss32/src/bindings/battlemsgdataview.cpp
index 99b72797..2c5bd55e 100644
--- a/mss32/src/bindings/battlemsgdataview.cpp
+++ b/mss32/src/bindings/battlemsgdataview.cpp
@@ -18,7 +18,11 @@
*/
#include "battlemsgdataview.h"
+#include "attackclasscat.h"
+#include "attackutils.h"
#include "battlemsgdata.h"
+#include "customattacks.h"
+#include "game.h"
#include "gameutils.h"
#include "idview.h"
#include "playerview.h"
@@ -27,6 +31,31 @@
namespace bindings {
+BattleTurnView::BattleTurnView(const game::CMidgardID& unitId,
+ char attackCount,
+ const game::IMidgardObjectMap* objectMap)
+ : unitId{unitId}
+ , objectMap{objectMap}
+ , attackCount{attackCount}
+{ }
+
+void BattleTurnView::bind(sol::state& lua)
+{
+ auto view = lua.new_usertype("BattleTurnView");
+ view["unit"] = sol::property(&BattleTurnView::getUnit);
+ view["attackCount"] = sol::property(&BattleTurnView::getAttackCount);
+}
+
+UnitView BattleTurnView::getUnit() const
+{
+ return UnitView{game::gameFunctions().findUnitById(objectMap, &unitId)};
+}
+
+int BattleTurnView::getAttackCount() const
+{
+ return attackCount;
+}
+
BattleMsgDataView::BattleMsgDataView(const game::BattleMsgData* battleMsgData,
const game::IMidgardObjectMap* objectMap)
: battleMsgData{battleMsgData}
@@ -36,13 +65,7 @@ BattleMsgDataView::BattleMsgDataView(const game::BattleMsgData* battleMsgData,
void BattleMsgDataView::bind(sol::state& lua)
{
auto view = lua.new_usertype("BattleView");
- view["getUnitStatus"] = &BattleMsgDataView::getUnitStatus;
- view["currentRound"] = sol::property(&BattleMsgDataView::getCurrentRound);
- view["autoBattle"] = sol::property(&BattleMsgDataView::getAutoBattle);
- view["attackerPlayer"] = sol::property(&BattleMsgDataView::getAttackerPlayer);
- view["defenderPlayer"] = sol::property(&BattleMsgDataView::getDefenderPlayer);
- view["attacker"] = sol::property(&BattleMsgDataView::getAttacker);
- view["defender"] = sol::property(&BattleMsgDataView::getDefender);
+ bindAccessMethods(view);
}
bool BattleMsgDataView::getUnitStatus(const IdView& unitId, int status) const
@@ -62,6 +85,11 @@ bool BattleMsgDataView::getAutoBattle() const
return game::BattleMsgDataApi::get().isAutoBattle(battleMsgData);
}
+bool BattleMsgDataView::getFastBattle() const
+{
+ return game::BattleMsgDataApi::get().isFastBattle(battleMsgData);
+}
+
std::optional BattleMsgDataView::getAttackerPlayer() const
{
return getPlayer(battleMsgData->attackerPlayerId);
@@ -92,6 +120,293 @@ std::optional BattleMsgDataView::getDefender() const
return GroupView{group, objectMap, &battleMsgData->defenderGroupId};
}
+game::RetreatStatus BattleMsgDataView::getRetreatStatus(bool attacker) const
+{
+ return game::BattleMsgDataApi::get().getRetreatStatus(battleMsgData, attacker);
+}
+
+bool BattleMsgDataView::isRetreatDecisionWasMade() const
+{
+ return game::BattleMsgDataApi::get().isRetreatDecisionWasMade(battleMsgData);
+}
+
+bool BattleMsgDataView::isUnitAttacker(const UnitView& unit) const
+{
+ return isUnitAttackerId(unit.getId());
+}
+
+bool BattleMsgDataView::isUnitAttackerId(const IdView& unitId) const
+{
+ return game::BattleMsgDataApi::get().isUnitAttacker(battleMsgData, &unitId.id);
+}
+
+bool BattleMsgDataView::isAfterBattle() const
+{
+ return game::BattleMsgDataApi::get().isAfterBattle(battleMsgData);
+}
+
+bool BattleMsgDataView::isDuel() const
+{
+ return battleMsgData->duel & 1u;
+}
+
+BattleMsgDataView::UnitActions BattleMsgDataView::getUnitActions(const UnitView& unit) const
+{
+ return getUnitActionsById(unit.getId());
+}
+
+BattleMsgDataView::UnitActions BattleMsgDataView::getUnitActionsById(const IdView& unitId) const
+{
+ using namespace game;
+
+ std::vector actions;
+ std::optional attackTargetGroup;
+ std::vector attackTargets;
+ std::optional item1TargetGroup;
+ std::vector item1Targets;
+ std::optional item2TargetGroup;
+ std::vector item2Targets;
+
+ {
+ const auto& setApi = IntSetApi::get();
+
+ Set battleActions;
+ setApi.constructor((IntSet*)&battleActions);
+
+ GroupIdTargetsPair attackTargetsPair{};
+ setApi.constructor(&attackTargetsPair.second);
+
+ GroupIdTargetsPair item1TargetsPair{};
+ setApi.constructor(&item1TargetsPair.second);
+
+ GroupIdTargetsPair item2TargetsPair{};
+ setApi.constructor(&item2TargetsPair.second);
+
+ game::BattleMsgDataApi::get().updateBattleActions(objectMap, battleMsgData, &unitId.id,
+ &battleActions, &attackTargetsPair,
+ &item1TargetsPair, &item2TargetsPair);
+
+ // Copy data into std containers for sol2
+ {
+ actions.reserve(battleActions.length);
+ for (auto& v : battleActions) {
+ actions.push_back(v);
+ }
+
+ if (const auto* group = hooks::getGroup(objectMap, &attackTargetsPair.first)) {
+ attackTargetGroup = bindings::GroupView{group, objectMap, &attackTargetsPair.first};
+ }
+
+ attackTargets.reserve(attackTargetsPair.second.length);
+ for (auto& v : attackTargetsPair.second) {
+ attackTargets.push_back(v);
+ }
+
+ if (const auto* group = hooks::getGroup(objectMap, &item1TargetsPair.first)) {
+ item1TargetGroup = bindings::GroupView{group, objectMap, &item1TargetsPair.first};
+ }
+
+ item1Targets.reserve(item1TargetsPair.second.length);
+ for (auto& v : item1TargetsPair.second) {
+ item1Targets.push_back(v);
+ }
+
+ if (const auto* group = hooks::getGroup(objectMap, &item2TargetsPair.first)) {
+ item2TargetGroup = bindings::GroupView{group, objectMap, &item2TargetsPair.first};
+ }
+
+ item2Targets.reserve(item2TargetsPair.second.length);
+ for (auto& v : item2TargetsPair.second) {
+ item2Targets.push_back(v);
+ }
+ }
+
+ setApi.destructor(&item2TargetsPair.second);
+ setApi.destructor(&item1TargetsPair.second);
+ setApi.destructor(&attackTargetsPair.second);
+ setApi.destructor((IntSet*)&battleActions);
+ }
+
+ return {actions, attackTargetGroup, attackTargets, item1TargetGroup,
+ item1Targets, item2TargetGroup, item2Targets};
+}
+
+int BattleMsgDataView::getUnitShatteredArmor(const UnitView& unit) const
+{
+ return getUnitShatteredArmorById(unit.getId());
+}
+
+int BattleMsgDataView::getUnitShatteredArmorById(const IdView& unitId) const
+{
+ return game::BattleMsgDataApi::get().getUnitShatteredArmor(battleMsgData, &unitId.id);
+}
+
+int BattleMsgDataView::getUnitFortificationArmor(const UnitView& unit) const
+{
+ return getUnitFortificationArmorById(unit.getId());
+}
+
+int BattleMsgDataView::getUnitFortificationArmorById(const IdView& unitId) const
+{
+ return game::BattleMsgDataApi::get().getUnitFortificationArmor(battleMsgData, &unitId.id);
+}
+
+bool BattleMsgDataView::isUnitResistantToSource(const UnitView& unit, int sourceId) const
+{
+ return isUnitResistantToSourceById(unit.getId(), sourceId);
+}
+
+bool BattleMsgDataView::isUnitResistantToSourceById(const IdView& unitId, int sourceId) const
+{
+ using namespace game;
+
+ auto attackSource{hooks::getAttackSourceById(static_cast(sourceId))};
+ if (!attackSource) {
+ return false;
+ }
+
+ return !BattleMsgDataApi::get().isUnitAttackSourceWardRemoved(battleMsgData, &unitId.id,
+ attackSource);
+}
+
+bool BattleMsgDataView::isUnitResistantToClass(const UnitView& unit, int classId) const
+{
+ return isUnitResistantToClassById(unit.getId(), classId);
+}
+
+bool BattleMsgDataView::isUnitResistantToClassById(const IdView& unitId, int classId) const
+{
+ using namespace game;
+
+ auto attackClass{hooks::getAttackClassById(static_cast(classId))};
+ if (!attackClass) {
+ return false;
+ }
+
+ return BattleMsgDataApi::get().isUnitAttackClassWardRemoved(battleMsgData, &unitId.id,
+ attackClass);
+}
+
+std::vector BattleMsgDataView::getTurnsOrder() const
+{
+ std::vector turns;
+
+ for (const auto& battleTurn : battleMsgData->turnsOrder) {
+ if (battleTurn.unitId == game::invalidId) {
+ break;
+ }
+
+ turns.emplace_back(battleTurn.unitId, battleTurn.attackCount, objectMap);
+ }
+
+ return turns;
+}
+
+bool BattleMsgDataView::isUnitRevived(const UnitView& unit) const
+{
+ return isUnitRevivedById(unit.getId());
+}
+
+bool BattleMsgDataView::isUnitRevivedById(const IdView& unitId) const
+{
+ const auto* info{game::BattleMsgDataApi::get().getUnitInfoById(battleMsgData, &unitId.id)};
+ if (!info) {
+ return false;
+ }
+
+ return info->unitFlags.parts.revived;
+}
+
+bool BattleMsgDataView::isUnitWaiting(const UnitView& unit) const
+{
+ return isUnitWaitingById(unit.getId());
+}
+
+bool BattleMsgDataView::isUnitWaitingById(const IdView& unitId) const
+{
+ const auto* info{game::BattleMsgDataApi::get().getUnitInfoById(battleMsgData, &unitId.id)};
+ if (!info) {
+ return false;
+ }
+
+ return info->unitFlags.parts.waited;
+}
+
+int BattleMsgDataView::getUnitDisableRound(const UnitView& unit) const
+{
+ return getUnitDisableRoundById(unit.getId());
+}
+
+int BattleMsgDataView::getUnitDisableRoundById(const IdView& unitId) const
+{
+ const auto* info{game::BattleMsgDataApi::get().getUnitInfoById(battleMsgData, &unitId.id)};
+ if (!info) {
+ return 0;
+ }
+
+ return info->disableAppliedRound;
+}
+
+int BattleMsgDataView::getUnitPoisonRound(const UnitView& unit) const
+{
+ return getUnitPoisonRoundById(unit.getId());
+}
+
+int BattleMsgDataView::getUnitPoisonRoundById(const IdView& unitId) const
+{
+ const auto* info{game::BattleMsgDataApi::get().getUnitInfoById(battleMsgData, &unitId.id)};
+ if (!info) {
+ return 0;
+ }
+
+ return info->poisonAppliedRound;
+}
+
+int BattleMsgDataView::getUnitFrostbiteRound(const UnitView& unit) const
+{
+ return getUnitFrostbiteRoundById(unit.getId());
+}
+
+int BattleMsgDataView::getUnitFrostbiteRoundById(const IdView& unitId) const
+{
+ const auto* info{game::BattleMsgDataApi::get().getUnitInfoById(battleMsgData, &unitId.id)};
+ if (!info) {
+ return 0;
+ }
+
+ return info->frostbiteAppliedRound;
+}
+
+int BattleMsgDataView::getUnitBlisterRound(const UnitView& unit) const
+{
+ return getUnitBlisterRoundById(unit.getId());
+}
+
+int BattleMsgDataView::getUnitBlisterRoundById(const IdView& unitId) const
+{
+ const auto* info{game::BattleMsgDataApi::get().getUnitInfoById(battleMsgData, &unitId.id)};
+ if (!info) {
+ return 0;
+ }
+
+ return info->blisterAppliedRound;
+}
+
+int BattleMsgDataView::getUnitTransformRound(const UnitView& unit) const
+{
+ return getUnitTransformRoundById(unit.getId());
+}
+
+int BattleMsgDataView::getUnitTransformRoundById(const IdView& unitId) const
+{
+ const auto* info{game::BattleMsgDataApi::get().getUnitInfoById(battleMsgData, &unitId.id)};
+ if (!info) {
+ return 0;
+ }
+
+ return info->transformAppliedRound;
+}
+
std::optional BattleMsgDataView::getPlayer(const game::CMidgardID& playerId) const
{
auto player{hooks::getPlayer(objectMap, &playerId)};
diff --git a/mss32/src/bindings/battlemsgdataviewmutable.cpp b/mss32/src/bindings/battlemsgdataviewmutable.cpp
new file mode 100644
index 00000000..a338a248
--- /dev/null
+++ b/mss32/src/bindings/battlemsgdataviewmutable.cpp
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2023 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "battlemsgdataviewmutable.h"
+#include "battlemsgdata.h"
+#include "gameutils.h"
+#include "idview.h"
+#include "playerview.h"
+#include "stackview.h"
+#include "unitview.h"
+#include
+
+namespace bindings {
+
+BattleMsgDataViewMutable::BattleMsgDataViewMutable(game::BattleMsgData* battleMsgData,
+ const game::IMidgardObjectMap* objectMap)
+ : BattleMsgDataView(battleMsgData, objectMap)
+ , battleMsgData{battleMsgData}
+ , objectMap{objectMap}
+{ }
+
+void BattleMsgDataViewMutable::bind(sol::state& lua)
+{
+ auto view = lua.new_usertype("BattleViewMutable", sol::base_classes,
+ sol::bases());
+ bindAccessMethods(view);
+
+ view["setRetreatStatus"] = &BattleMsgDataViewMutable::setRetreatStatus;
+ view["setDecidedToRetreat"] = &BattleMsgDataViewMutable::setDecidedToRetreat;
+}
+
+void BattleMsgDataViewMutable::setRetreatStatus(bool attacker, std::uint8_t value)
+{
+ game::BattleMsgDataApi::get().setRetreatStatus(battleMsgData, attacker,
+ static_cast(value & 3u));
+}
+
+void BattleMsgDataViewMutable::setDecidedToRetreat()
+{
+ game::BattleMsgDataApi::get().setRetreatDecisionWasMade(battleMsgData);
+}
+
+} // namespace bindings
diff --git a/mss32/src/bindings/buildingview.cpp b/mss32/src/bindings/buildingview.cpp
new file mode 100644
index 00000000..18b15008
--- /dev/null
+++ b/mss32/src/bindings/buildingview.cpp
@@ -0,0 +1,101 @@
+/*
+ * This file is part of the modding toolset for Disciples 2.
+ * (https://github.com/VladimirMakeev/D2ModdingToolset)
+ * Copyright (C) 2024 Vladimir Makeev.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "buildingview.h"
+#include "buildingtype.h"
+#include "globaldata.h"
+#include
+
+namespace bindings {
+
+BuildingView::BuildingView(const game::TBuildingType* building)
+ : building{building}
+{ }
+
+void BuildingView::bind(sol::state& lua)
+{
+ auto view = lua.new_usertype("BuildingView");
+ view["id"] = sol::property(&BuildingView::getId);
+ view["cost"] = sol::property(&BuildingView::getCost);
+ view["type"] = sol::property(&BuildingView::getCategory);
+ view["requiredBuilding"] = sol::property(&BuildingView::getRequiredBuilding);
+ view["branch"] = sol::property(&BuildingView::getUnitBranch);
+ view["level"] = sol::property(&BuildingView::getLevel);
+}
+
+IdView BuildingView::getId() const
+{
+ return IdView{building->id};
+}
+
+CurrencyView BuildingView::getCost() const
+{
+ return CurrencyView{building->data->cost};
+}
+
+int BuildingView::getCategory() const
+{
+ return static_cast