diff --git a/TemplePlus/TemplePlus.vcxproj b/TemplePlus/TemplePlus.vcxproj
index 6127348df..1b7ecb648 100644
--- a/TemplePlus/TemplePlus.vcxproj
+++ b/TemplePlus/TemplePlus.vcxproj
@@ -171,6 +171,7 @@
+
@@ -319,6 +320,7 @@
+
diff --git a/TemplePlus/TemplePlus.vcxproj.filters b/TemplePlus/TemplePlus.vcxproj.filters
index 5a4748146..74cc0bc78 100644
--- a/TemplePlus/TemplePlus.vcxproj.filters
+++ b/TemplePlus/TemplePlus.vcxproj.filters
@@ -451,6 +451,9 @@
ui
+
+ ui
+
@@ -849,6 +852,9 @@
ui
+
+ ui
+
diff --git a/TemplePlus/ai.cpp b/TemplePlus/ai.cpp
index 8c517ac22..c757f9c6b 100644
--- a/TemplePlus/ai.cpp
+++ b/TemplePlus/ai.cpp
@@ -302,6 +302,11 @@ void AiSystem::StopAttacking(objHndl npc) {
_StopAttacking(npc);
}
+void AiSystem::ProvokeHostility(objHndl agitator, objHndl provokedNpc, int rangeType, int flags)
+{
+ temple::GetRef(0x1005E8D0)(agitator, provokedNpc, rangeType, flags);
+}
+
objHndl AiSystem::GetCombatFocus(objHndl npc) {
auto obj = objSystem->GetObject(npc);
return obj->GetObjHndl(obj_f_npc_combat_focus);
diff --git a/TemplePlus/ai.h b/TemplePlus/ai.h
index 82f85aa05..8e101bdb7 100644
--- a/TemplePlus/ai.h
+++ b/TemplePlus/ai.h
@@ -127,6 +127,7 @@ struct AiSystem : temple::AddressTable
void ShitlistRemove(objHndl npc, objHndl target);
void FleeAdd(objHndl npc, objHndl target);
void StopAttacking(objHndl npc);
+ void ProvokeHostility(objHndl agitator, objHndl provokedNpc, int rangeType, int flags); // rangeType - 0 is for 5 tiles, 1 is for 10 tiles, 2 is for 20 tiles, and 3 is unlimited
objHndl GetCombatFocus(objHndl npc);
objHndl GetWhoHitMeLast(objHndl npc);
diff --git a/TemplePlus/anim.cpp b/TemplePlus/anim.cpp
index 9fc053299..c603e0fe8 100644
--- a/TemplePlus/anim.cpp
+++ b/TemplePlus/anim.cpp
@@ -483,6 +483,10 @@ int AnimationGoals::PushAttemptAttack(objHndl attacker, objHndl defender) {
return addresses.PushAttemptAttack(attacker, defender);
}
+int AnimationGoals::PushDodge(objHndl attacker, objHndl dodger){
+ return temple::GetRef(0x100158E0)(attacker, dodger);
+}
+
int AnimationGoals::PushAnimate(objHndl obj, int anim) {
return addresses.PushAnimate(obj, anim);
}
diff --git a/TemplePlus/anim.h b/TemplePlus/anim.h
index 04adf7bf1..9b5f83724 100644
--- a/TemplePlus/anim.h
+++ b/TemplePlus/anim.h
@@ -50,7 +50,7 @@ enum AnimGoalType : uint32_t {
ag_shoot_spell = 0x1D,
ag_hit_by_spell = 0x1E,
ag_hit_by_weapon = 0x1F,
- ag_dodge = 0x20,
+ ag_dodge = 0x20, // this is where the enums start to be off compared to the debug string in the dll (they added ag_dodge and didn't update the list)
ag_dying,
ag_destroy_obj,
ag_use_skill_on,
@@ -153,6 +153,7 @@ class AnimationGoals {
int PushAttackAnim(objHndl actor, objHndl target, int unk1, int hitAnimIdx, int playCrit, int useSecondaryAnim);
int GetActionAnimId(objHndl objHndl);
int PushAttemptAttack(objHndl attacker, objHndl defender);
+ int PushDodge(objHndl attacker, objHndl dodger);
int PushAnimate(objHndl obj, int anim);
BOOL PushSpellInterrupt(const objHndl& caster, objHndl item, AnimGoalType animGoalType, int spellSchool);
};
diff --git a/TemplePlus/d20.cpp b/TemplePlus/d20.cpp
index c8275988a..0b607be58 100644
--- a/TemplePlus/d20.cpp
+++ b/TemplePlus/d20.cpp
@@ -1761,8 +1761,8 @@ BOOL D20ActionCallbacks::ActionFrameStandardAttack(D20Actn* d20a){
histSys.CreateRollHistoryString(d20a->rollHistId1);
histSys.CreateRollHistoryString(d20a->rollHistId2);
histSys.CreateRollHistoryString(d20a->rollHistId0);
- auto makeAttack = temple::GetRef(0x100B7950);
- makeAttack(d20a->d20APerformer, d20a->d20ATarget, d20a->data1, static_cast(d20a->d20Caf), d20a->d20ActType);
+ //auto makeAttack = temple::GetRef(0x100B7950);
+ damage.DealAttackDamage(d20a->d20APerformer, d20a->d20ATarget, d20a->data1, static_cast(d20a->d20Caf), d20a->d20ActType);
return TRUE;
}
diff --git a/TemplePlus/damage.cpp b/TemplePlus/damage.cpp
index d8152aabb..93343822b 100644
--- a/TemplePlus/damage.cpp
+++ b/TemplePlus/damage.cpp
@@ -3,6 +3,16 @@
#include "damage.h"
#include "dice.h"
#include "bonus.h"
+#include "ai.h"
+#include "gamesystems/objects/objsystem.h"
+#include "critter.h"
+#include "weapon.h"
+#include "combat.h"
+#include "history.h"
+#include "float_line.h"
+#include "sound.h"
+#include "anim.h"
+#include "ui/ui_logbook.h"
static_assert(temple::validate_size::value, "DispIoDamage");
@@ -119,6 +129,11 @@ int DamagePacket::AddPhysicalDR(int amount, int bypasserBitmask, int damageMesLi
return damage.AddPhysicalDR(this, amount, bypasserBitmask, (unsigned)damageMesLine);
}
+void DamagePacket::AddAttackPower(int attackPower)
+{
+ this->attackPowerType |= attackPower;
+}
+
void DamagePacket::CalcFinalDamage(){ // todo hook this
for (auto i=0u; i < this->diceCount; i++){
auto &dice = this->dice[i];
@@ -155,16 +170,291 @@ int DamagePacket::GetOverallDamageByType(DamageType damType)
return damTot;
}
+DamagePacket::DamagePacket(){
+ diceCount = 0;
+ damResCount = 0;
+ damModCount = 0;
+ attackPowerType = 0;
+ finalDamage = 0;
+ flags = 0;
+ description = nullptr;
+ critHitMultiplier = 1;
+
+}
+
void Damage::DealDamage(objHndl victim, objHndl attacker, const Dice& dice, DamageType type, int attackPower, int reduction, int damageDescId, D20ActionType actionType) {
addresses.DoDamage(victim, attacker, dice.ToPacked(), type, attackPower, reduction, damageDescId, actionType);
}
-void Damage::DealSpellDamage(objHndl victim, objHndl attacker, const Dice& dice, DamageType type, int attackPower, int reduction, int damageDescId, D20ActionType actionType, int spellId, int flags) {
+void Damage::DealSpellDamage(objHndl tgt, objHndl attacker, const Dice& dice, DamageType type, int attackPower, int reduction, int damageDescId, D20ActionType actionType, int spellId, int flags) {
+
+ SpellPacketBody spPkt(spellId);
+ if (!tgt)
+ return;
+
+ if (attacker && attacker != tgt && critterSys.AllegianceShared(tgt, attacker))
+ floatSys.FloatCombatLine(tgt, 107); // friendly fire
+
+ aiSys.ProvokeHostility(attacker, tgt, 1, 0);
+
+ if (critterSys.IsDeadNullDestroyed(tgt))
+ return;
+
+ DispIoDamage evtObjDam;
+ evtObjDam.attackPacket.d20ActnType = actionType;
+ evtObjDam.attackPacket.attacker = attacker;
+ evtObjDam.attackPacket.victim = tgt;
+ evtObjDam.attackPacket.dispKey = 1;
+ evtObjDam.attackPacket.flags = (D20CAF)(flags | D20CAF_HIT);
+
+ if (attacker && objects.IsCritter(attacker)){
+ if (flags & D20CAF_SECONDARY_WEAPON)
+ evtObjDam.attackPacket.weaponUsed = inventory.ItemWornAt(attacker, EquipSlot::WeaponSecondary);
+ else
+ evtObjDam.attackPacket.weaponUsed = inventory.ItemWornAt(attacker, EquipSlot::WeaponPrimary);
+
+ if (evtObjDam.attackPacket.weaponUsed && objects.GetType(evtObjDam.attackPacket.weaponUsed) != obj_t_weapon)
+ evtObjDam.attackPacket.weaponUsed = objHndl::null;
+
+ evtObjDam.attackPacket.ammoItem = combatSys.CheckRangedWeaponAmmo(attacker);
+ } else
+ {
+ evtObjDam.attackPacket.weaponUsed = objHndl::null;
+ evtObjDam.attackPacket.ammoItem = objHndl::null;
+ }
+
+ if (reduction != 100){
+ addresses.AddDamageModFactor(&evtObjDam.damage, reduction * 0.01f, type, damageDescId);
+ }
+
+ evtObjDam.damage.AddDamageDice(dice.ToPacked(), type, 103);
+ evtObjDam.damage.AddAttackPower(attackPower);
+ auto mmData = (MetaMagicData)spPkt.metaMagicData;
+ if (mmData.metaMagicEmpowerSpellCount)
+ evtObjDam.damage.flags |= 2; // empowered
+ if (mmData.metaMagicFlags & 1)
+ evtObjDam.damage.flags |= 1; // maximized
+ temple::GetRef(0x10BCA8AC) = 0; // is weapon damage
+
+ DamageCritter(attacker, tgt, evtObjDam);
+
+ //addresses.DoSpellDamage(tgt, attacker, dice.ToPacked(), type, attackPower, reduction, damageDescId, actionType, spellId, flags);
+
+}
+
+int Damage::DealAttackDamage(objHndl attacker, objHndl tgt, int d20Data, D20CAF flags, D20ActionType actionType)
+{
+ aiSys.ProvokeHostility(attacker, tgt, 1, 0);
+
+ auto tgtObj = objSystem->GetObject(tgt);
+ if (critterSys.IsDeadNullDestroyed(tgt)){
+ return -1;
+ }
+
+ DispIoDamage evtObjDam;
+ evtObjDam.attackPacket.d20ActnType = actionType;
+ evtObjDam.attackPacket.attacker = attacker;
+ evtObjDam.attackPacket.victim = tgt;
+ evtObjDam.attackPacket.dispKey = d20Data;
+ evtObjDam.attackPacket.flags = flags;
+
+ auto &weaponUsed = evtObjDam.attackPacket.weaponUsed;
+ if (flags & D20CAF_SECONDARY_WEAPON)
+ weaponUsed = inventory.ItemWornAt(attacker, EquipSlot::WeaponSecondary);
+ else
+ weaponUsed = inventory.ItemWornAt(attacker, EquipSlot::WeaponPrimary);
+
+ if (weaponUsed && objects.GetType(weaponUsed) != obj_t_weapon){
+ weaponUsed = objHndl::null;
+ }
+
+ evtObjDam.attackPacket.ammoItem = combatSys.CheckRangedWeaponAmmo(attacker);
+
+ if ( flags & D20CAF_CONCEALMENT_MISS){
+ histSys.CreateRollHistoryLineFromMesfile(11, attacker, tgt);
+ floatSys.FloatCombatLine(attacker, 45); // Miss (Concealment)!
+ auto soundId = inventory.GetSoundIdForItemEvent(weaponUsed, attacker, tgt, 6);
+ sound.PlaySoundAtObj(soundId, attacker);
+ d20Sys.d20SendSignal(attacker, DK_SIG_Attack_Made, (int)&evtObjDam, 0);
+ return -1;
+ }
+
+ if (!(flags & D20CAF_HIT)) {
+ floatSys.FloatCombatLine(attacker, 29);
+ d20Sys.d20SendSignal(attacker, DK_SIG_Attack_Made, (int)&evtObjDam, 0);
+
+ auto soundId = inventory.GetSoundIdForItemEvent(weaponUsed, attacker, tgt, 6);
+ sound.PlaySoundAtObj(soundId, attacker);
+
+ if (flags & D20CAF_DEFLECT_ARROWS){
+ floatSys.FloatCombatLine(tgt, 5052);
+ histSys.CreateRollHistoryLineFromMesfile(12, attacker, tgt);
+ }
+
+ // dodge animation
+ if (!critterSys.IsDeadOrUnconscious(tgt) && !critterSys.IsProne(tgt)){
+ animationGoals.PushDodge(attacker, tgt);
+ }
+ return -1;
+ }
+
+ if (tgt && attacker && critterSys.AllegianceShared(tgt, attacker)){ // TODO check that this solves the infamous "Friendly Fire" float for NPCs
+ floatSys.FloatCombatLine(tgt, 107); // Friendly Fire
+ }
- addresses.DoSpellDamage(victim, attacker, dice.ToPacked(), type, attackPower, reduction, damageDescId, actionType, spellId, flags);
+ auto isUnconsciousAlready = critterSys.IsDeadOrUnconscious(tgt);
+
+
+ dispatch.DispatchDamage(attacker, &evtObjDam, dispTypeDealingDamage, DK_NONE);
+ if (evtObjDam.attackPacket.flags & D20CAF_CRITICAL){
+
+ // get extra Hit Dice and apply them
+ DispIoAttackBonus evtObjCritDice;
+ evtObjCritDice.attackPacket.victim = tgt;
+ evtObjCritDice.attackPacket.d20ActnType = evtObjDam.attackPacket.d20ActnType;
+ evtObjCritDice.attackPacket.attacker = attacker;
+ evtObjCritDice.attackPacket.dispKey = d20Data;
+ evtObjCritDice.attackPacket.flags = evtObjDam.attackPacket.flags;
+ if (evtObjDam.attackPacket.flags & D20CAF_SECONDARY_WEAPON){
+ evtObjCritDice.attackPacket.weaponUsed = inventory.ItemWornAt(attacker, EquipSlot::WeaponSecondary);
+ } else
+ evtObjCritDice.attackPacket.weaponUsed = inventory.ItemWornAt(attacker, EquipSlot::WeaponPrimary);
+ if (evtObjCritDice.attackPacket.weaponUsed && objects.GetType(evtObjCritDice.attackPacket.weaponUsed) != obj_t_weapon)
+ evtObjCritDice.attackPacket.weaponUsed = objHndl::null;
+ evtObjCritDice.attackPacket.ammoItem = combatSys.CheckRangedWeaponAmmo(attacker);
+ auto extraHitDice = dispatch.DispatchAttackBonus(attacker, objHndl::null, &evtObjCritDice, dispTypeGetCriticalHitExtraDice, DK_NONE);
+ auto critMultiplierApply = temple::GetRef(0x100E1640); // damagepacket, multiplier, damage.mes line
+ critMultiplierApply(evtObjDam.damage, extraHitDice + 1, 102);
+ floatSys.FloatCombatLine(attacker, 12);
+
+ // play sound
+ auto soundId = critterSys.SoundmapCritter(tgt, 0);
+ sound.PlaySoundAtObj(soundId, tgt);
+ soundId = inventory.GetSoundIdForItemEvent(evtObjCritDice.attackPacket.weaponUsed, attacker, tgt, 7);
+ sound.PlaySoundAtObj(soundId, attacker);
+
+ // increase crit hits in logbook
+ uiLogbook.IncreaseCritHits(attacker);
+ } else
+ {
+ auto soundId = inventory.GetSoundIdForItemEvent(evtObjDam.attackPacket.weaponUsed, attacker, tgt, 5);
+ sound.PlaySoundAtObj(soundId, attacker);
+ }
+
+ temple::GetRef(0x10BCA8AC) = 1; // physical damage Flag used for logbook recording
+ DamageCritter(attacker, tgt, evtObjDam);
+
+ // play damage effect particles
+ for (auto i=0; i < evtObjDam.damage.diceCount; i++){
+ temple::GetRef(0x10016A90)(tgt, evtObjDam.damage.dice[i].type, evtObjDam.damage.dice[i].rolledDamage);
+ }
+
+
+ // signal events
+ if (!isUnconsciousAlready && critterSys.IsDeadOrUnconscious(tgt)){
+ d20Sys.d20SendSignal(attacker, DK_SIG_Dropped_Enemy, (int)&evtObjDam, 0);
+ }
+
+ return addresses.GetDamageTypeOverallDamage(&evtObjDam.damage, DamageType::Unspecified);
+}
+
+int Damage::DealWeaponlikeSpellDamage(objHndl tgt, objHndl attacker, const Dice & dice, DamageType type, int attackPower, int damFactor, int damageDescId, D20ActionType actionType, int spellId, D20CAF flags, int prjoectileIdx)
+{
+
+ SpellPacketBody spPkt(spellId);
+ if (!tgt)
+ return -1;
+
+ if (attacker && attacker != tgt && critterSys.AllegianceShared(tgt, attacker))
+ floatSys.FloatCombatLine(tgt, 107); // friendly fire
+
+ aiSys.ProvokeHostility(attacker, tgt, 1, 0);
+
+ if (critterSys.IsDeadNullDestroyed(tgt))
+ return -1;
+
+ DispIoDamage evtObjDam;
+ evtObjDam.attackPacket.d20ActnType = actionType;
+ evtObjDam.attackPacket.attacker = attacker;
+ evtObjDam.attackPacket.victim = tgt;
+ evtObjDam.attackPacket.dispKey = prjoectileIdx;
+ evtObjDam.attackPacket.flags = (D20CAF)(flags | D20CAF_HIT);
+
+ if (attacker && objects.IsCritter(attacker)) {
+ if (flags & D20CAF_SECONDARY_WEAPON)
+ evtObjDam.attackPacket.weaponUsed = inventory.ItemWornAt(attacker, EquipSlot::WeaponSecondary);
+ else
+ evtObjDam.attackPacket.weaponUsed = inventory.ItemWornAt(attacker, EquipSlot::WeaponPrimary);
+
+ if (evtObjDam.attackPacket.weaponUsed && objects.GetType(evtObjDam.attackPacket.weaponUsed) != obj_t_weapon)
+ evtObjDam.attackPacket.weaponUsed = objHndl::null;
+
+ evtObjDam.attackPacket.ammoItem = combatSys.CheckRangedWeaponAmmo(attacker);
+ }
+ else
+ {
+ evtObjDam.attackPacket.weaponUsed = objHndl::null;
+ evtObjDam.attackPacket.ammoItem = objHndl::null;
+ }
+
+ if (damFactor != 100) {
+ addresses.AddDamageModFactor(&evtObjDam.damage, damFactor * 0.01f, type, damageDescId);
+ }
+
+
+ if (flags & D20CAF_CONCEALMENT_MISS) {
+ histSys.CreateRollHistoryLineFromMesfile(11, attacker, tgt);
+ floatSys.FloatCombatLine(attacker, 45); // Miss (Concealment)!
+ // d20Sys.d20SendSignal(attacker, DK_SIG_Attack_Made, (int)&evtObjDam, 0); // casting a spell isn't considered an attack action
+ return -1;
+ }
+
+ if (!(flags & D20CAF_HIT)) {
+ floatSys.FloatCombatLine(attacker, 29);
+
+ // dodge animation
+ if (!critterSys.IsDeadOrUnconscious(tgt) && !critterSys.IsProne(tgt)) {
+ animationGoals.PushDodge(attacker, tgt);
+ }
+ return -1;
+ }
+
+ // get damage dice
+ evtObjDam.damage.AddDamageDice(dice.ToPacked(), type, 103);
+ evtObjDam.damage.AddAttackPower(attackPower);
+ auto mmData = (MetaMagicData)spPkt.metaMagicData;
+ if (mmData.metaMagicEmpowerSpellCount)
+ evtObjDam.damage.flags |= 2; // empowered
+ if (mmData.metaMagicFlags & 1)
+ evtObjDam.damage.flags |= 1; // maximized
+ dispatch.DispatchDamage(attacker, &evtObjDam, dispTypeDealingDamageWeaponlikeSpell, DK_NONE);
+
+ if (evtObjDam.attackPacket.flags & D20CAF_CRITICAL) {
+ auto extraHitDice = dice.GetCount();
+ auto critMultiplierApply = temple::GetRef(0x100E1640); // damagepacket, multiplier, damage.mes line
+ critMultiplierApply(evtObjDam.damage, extraHitDice + 1, 102);
+ floatSys.FloatCombatLine(attacker, 12);
+
+ // play sound
+ auto soundId = critterSys.SoundmapCritter(tgt, 0);
+ sound.PlaySoundAtObj(soundId, tgt);
+
+ // increase crit hits in logbook
+ uiLogbook.IncreaseCritHits(attacker);
+ }
+
+
+ temple::GetRef(0x10BCA8AC) = 0; // is weapon damage
+
+ DamageCritter(attacker, tgt, evtObjDam);
+
+ return -1;
+}
+void Damage::DamageCritter(objHndl attacker, objHndl tgt, DispIoDamage & evtObjDam){
+ temple::GetRef(0x100B6B30)(attacker, tgt, evtObjDam);
}
void Damage::Heal(objHndl target, objHndl healer, const Dice& dice, D20ActionType actionType) {
diff --git a/TemplePlus/damage.h b/TemplePlus/damage.h
index 6556e45a9..11bc6999c 100644
--- a/TemplePlus/damage.h
+++ b/TemplePlus/damage.h
@@ -50,8 +50,11 @@ struct DamagePacket {
int AddDamageDice(uint32_t dicePacked, DamageType damType, int damageMesLine, const char* description = nullptr);
BOOL AddDamageBonus(int32_t damBonus, int bonType, int bonMesline, const char* causeDesc = nullptr);
int AddPhysicalDR(int amount, int bypasserBitmask, int damageMesLine);
+ void AddAttackPower(int attackPower);
void CalcFinalDamage(); // calcualtes the finalDamage field
int GetOverallDamageByType(DamageType damType);
+
+ DamagePacket();
};
#pragma pack(push, 1)
@@ -62,6 +65,7 @@ struct DispIoDamage : DispIO { // Io type 4
DispIoDamage() {
dispIOType = dispIOTypeDamage;
+ attackPacket.d20ActnType = D20A_NONE;
}
};
#pragma pack(pop)
@@ -90,6 +94,18 @@ class Damage {
void DealSpellDamage(objHndl victim, objHndl attacker, const Dice &dice, DamageType type, int attackPower, int reduction, int damageDescId, D20ActionType actionType,
int spellId, int flags);
+ /*
+ deals damage from a successful weapon attack
+ */
+ int DealAttackDamage(objHndl attacker, objHndl tgt, int d20Data, D20CAF flags, D20ActionType actionType);
+ /*
+ used for spells that have an attack roll
+ */
+ int DealWeaponlikeSpellDamage(objHndl tgt, objHndl attacker, const Dice &dice, DamageType type, int attackPower, int damFactor, int damageDescId, D20ActionType actionType, int spellId, D20CAF flags, int projectileIdx = 0);
+
+ void DamageCritter(objHndl attacker, objHndl tgt, DispIoDamage & evtObjDam);
+
+
void Heal(objHndl target, objHndl healer, const Dice &dice, D20ActionType actionType);
void HealSpell(objHndl target, objHndl healer, const Dice &dice, D20ActionType actionType, int spellId);
diff --git a/TemplePlus/inventory.cpp b/TemplePlus/inventory.cpp
index f378c3b3e..5564c47c7 100644
--- a/TemplePlus/inventory.cpp
+++ b/TemplePlus/inventory.cpp
@@ -866,3 +866,8 @@ const std::string & InventorySystem::GetAttachBone(objHndl handle)
return sBoneNames[slot - 200];
}
+
+int InventorySystem::GetSoundIdForItemEvent(objHndl item, objHndl wielder, objHndl tgt, int eventType)
+{
+ return temple::GetRef(0x1006E0B0)(item, wielder, tgt, eventType);
+}
diff --git a/TemplePlus/inventory.h b/TemplePlus/inventory.h
index 73edce626..da5c1fcdf 100644
--- a/TemplePlus/inventory.h
+++ b/TemplePlus/inventory.h
@@ -158,6 +158,8 @@ struct InventorySystem : temple::AddressTable
// spawn the items for this object according to invensource.mes
int (__cdecl*SpawnInvenSourceItems)(objHndl obj);
+ int GetSoundIdForItemEvent(objHndl item, objHndl wielder, objHndl tgt, int eventType);
+
InventorySystem()
{
rebase(GetSubstituteInventory, 0x1007F5B0);
diff --git a/TemplePlus/python/python_object.cpp b/TemplePlus/python/python_object.cpp
index 5c724817e..f9dce85ff 100644
--- a/TemplePlus/python/python_object.cpp
+++ b/TemplePlus/python/python_object.cpp
@@ -1104,6 +1104,25 @@ static PyObject* PyObjHandle_SpellDamageWithReduction(PyObject* obj, PyObject* a
Py_RETURN_NONE;
}
+static PyObject* PyObjHandle_SpellDamageWeaponlike(PyObject* obj, PyObject* args) {
+ auto self = GetSelf(obj);
+ objHndl attacker;
+ Dice dice;
+ DamageType type;
+ int attackPowerType = 0;
+ int reduction = 100;
+ D20ActionType actionType = D20A_NONE;
+ int spellId = 0, projectileIdx = 0;
+ D20CAF flags;
+ if (!PyArg_ParseTuple(args, "O&iO&|iiiiii:objhndl.spell_damage_weaponlike", &ConvertObjHndl, &attacker, &type, &ConvertDice, &dice, &attackPowerType, &reduction, &actionType, &spellId, &flags, &projectileIdx)) {
+ return 0;
+ }
+ // Line 105: Saving Throw
+ damage.DealWeaponlikeSpellDamage(self->handle, attacker, dice, type, attackPowerType, reduction, 105, actionType, spellId, flags, projectileIdx);
+ Py_RETURN_NONE;
+}
+
+
static PyObject* PyObjHandle_StealFrom(PyObject* obj, PyObject* args) {
auto self = GetSelf(obj);
objHndl target;
@@ -1577,13 +1596,14 @@ static PyObject* PyObjHandle_PerformTouchAttack(PyObject* obj, PyObject* args) {
d20Sys.CreateRollHistory(action.rollHistId2);
d20Sys.CreateRollHistory(action.rollHistId0);
- if (action.d20Caf & D20CAF_CRITICAL) {
+ /*if (action.d20Caf & D20CAF_CRITICAL) {
return PyInt_FromLong(D20CAF_CRITICAL);
} else if (action.d20Caf & D20CAF_HIT) {
return PyInt_FromLong(D20CAF_HIT);
} else {
return PyInt_FromLong(0);
- }
+ }*/
+ return PyInt_FromLong(action.d20Caf);
}
static PyObject* PyObjHandle_AddToInitiative(PyObject* obj, PyObject* args) {
@@ -2813,6 +2833,7 @@ static PyMethodDef PyObjHandleMethods[] = {
{ "spell_memorized_add", PyObjHandle_SpellMemorizedAdd, METH_VARARGS, NULL },
{ "spell_damage", PyObjHandle_SpellDamage, METH_VARARGS, NULL },
{ "spell_damage_with_reduction", PyObjHandle_SpellDamageWithReduction, METH_VARARGS, NULL },
+ { "spell_damage_weaponlike", PyObjHandle_SpellDamageWeaponlike, METH_VARARGS, NULL },
{ "spell_heal", PyObjHandle_SpellHeal, METH_VARARGS, NULL },
{ "spells_pending_to_memorized", PyObjHandle_PendingToMemorized, METH_VARARGS, NULL },
{ "spells_cast_reset", PyObjHandle_SpellsCastReset, METH_VARARGS, NULL },
diff --git a/TemplePlus/temple_enums.h b/TemplePlus/temple_enums.h
index 3758a6ca3..6eef28c94 100644
--- a/TemplePlus/temple_enums.h
+++ b/TemplePlus/temple_enums.h
@@ -1896,6 +1896,10 @@ enum enum_disp_type : uint32_t {
dispTypeSpellListExtension, // NEW! used for extending spell-casting classes by other classes (as with Prestige Classes)
dispTypeGetBaseCasterLevel,
dispTypeLevelupSystemEvent,
+
+
+
+ dispTypeDealingDamageWeaponlikeSpell,
dispTypeCount // used just for size definition purposes
diff --git a/TemplePlus/ui/ui_logbook.cpp b/TemplePlus/ui/ui_logbook.cpp
new file mode 100644
index 000000000..bcca657ce
--- /dev/null
+++ b/TemplePlus/ui/ui_logbook.cpp
@@ -0,0 +1,46 @@
+#include "stdafx.h"
+#include "ui_logbook.h"
+#include
+#include
+#include
+#include
+#include
+
+
+UiLogbook uiLogbook;
+
+void UiLogbook::IncreaseAmount(PartyLogbookPacket & pkt, objHndl handle, int amount){
+
+ if (pkt.HasObj(handle) || pkt.AddObj(handle)){
+ for (auto i=0; i < LOGBOOK_MAX_PARTY_MEMBER_COUNT; i++){
+ if (objSystem->GetHandleById(pkt.sub[i].id) == handle){
+ pkt.sub[i].amount += amount;
+ break;
+ }
+ }
+ }
+}
+
+void UiLogbook::IncreaseCritHits(objHndl handle){
+ if (!handle)
+ return;
+
+ if (objects.GetType(handle) == obj_t_pc && party.IsInParty(handle)){
+ IncreaseAmount( temple::GetRef(0x10D24878), handle, 1);
+ }
+}
+
+BOOL PartyLogbookPacket::HasObj(objHndl handle){
+ for (auto i=0; i < LOGBOOK_MAX_PARTY_MEMBER_COUNT; i++){
+ ObjectId id = this->sub[i].id;
+
+ if (objSystem->GetHandleById(id) == handle)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+BOOL PartyLogbookPacket::AddObj(objHndl handle){
+ return temple::GetRef(0x10198C40)(this, handle);
+}
diff --git a/TemplePlus/ui/ui_logbook.h b/TemplePlus/ui/ui_logbook.h
new file mode 100644
index 000000000..35e094823
--- /dev/null
+++ b/TemplePlus/ui/ui_logbook.h
@@ -0,0 +1,32 @@
+#pragma once
+#include "common.h"
+
+#define LOGBOOK_MAX_PARTY_MEMBER_COUNT 5
+
+
+struct LogbookEntry
+{
+ ObjectId id;
+ int amount;
+ int protoId;
+};
+
+struct PartyLogbookPacket
+{
+ LogbookEntry sub[LOGBOOK_MAX_PARTY_MEMBER_COUNT];
+ BOOL HasObj(objHndl handle);
+ BOOL AddObj(objHndl handle);
+};
+
+class UiLogbook
+{
+public:
+ void IncreaseCritHits(objHndl handle);
+
+protected:
+ void IncreaseAmount(PartyLogbookPacket & pkt, objHndl handle, int amount);
+
+};
+
+
+extern UiLogbook uiLogbook;
\ No newline at end of file