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