Skip to content

Commit

Permalink
Fix & test OpenEnroth#1331
Browse files Browse the repository at this point in the history
  • Loading branch information
captainurist committed Oct 13, 2023
1 parent abb6164 commit d533f43
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 2 deletions.
4 changes: 3 additions & 1 deletion src/Engine/Objects/Character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1257,8 +1257,10 @@ int Character::CalculateRangedDamageTo(MonsterId uMonsterInfoID) {
} else if (itemenchant == ITEM_ENCHANTMENT_ELF_SLAYING &&
supertypeForMonsterId(uMonsterInfoID) == MONSTER_SUPERTYPE_ELF) { // double vs elf
damage *= 2;
} else if (itemenchant == ITEM_ENCHANTMENT_TITAN_SLAYING &&
supertypeForMonsterId(uMonsterInfoID) == MONSTER_SUPERTYPE_TITAN) { // double vs titans
damage *= 2;
}
// TODO(captainurist): Do bows of titan slaying exist? If they do, then they don't do x2 damage against titans.
}

return damage + this->GetSkillBonus(CHARACTER_ATTRIBUTE_RANGED_DMG_BONUS);
Expand Down
2 changes: 1 addition & 1 deletion test/Bin/GameTest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if(OE_BUILD_TESTS)
ExternalProject_Add(OpenEnroth_TestData
PREFIX ${CMAKE_CURRENT_BINARY_DIR}/test_data_tmp
GIT_REPOSITORY https://github.com/OpenEnroth/OpenEnroth_TestData.git
GIT_TAG 0a6f90ea43dfa8d8e7990474624cf9f52e969b5f
GIT_TAG 94bdd06f36eabe78f51e7582fda7ea6d1a717c52
SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/test_data
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
Expand Down
20 changes: 20 additions & 0 deletions test/Bin/GameTest/GameTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1685,3 +1685,23 @@ GAME_TEST(Prs, Pr1325) {
EXPECT_EQ(vialsTape.delta(), +6);
EXPECT_EQ(deadTape.delta(), +84); // Too much armageddon...
}

GAME_TEST(Issues, Issue1331) {
// "of David" enchanted bows should do double damage against Titans.
auto hpsTape = actorTapes.hps({31, 33});
auto deadTape = actorTapes.indicesByState(AIState::Dead);
test.playTraceFromTestData("issue_1331.mm7", "issue_1331.json");
EXPECT_EQ(deadTape.frontBack(), tape(std::initializer_list<int>{}, {31, 33})); // Check that titans are dead.

// Damage as stated in the character sheet is 41-45. Crossbow is 4d2+7. Because of how non-random engine works,
// 4d2+7 will always roll 13, and thus the 41-45 range is effectively compressed into 43-43.
//
// With the "of David" enchantment, the 4d2+7 part of the damage is doubled, so max damage is now 43+13=56.
//
// Min damage is so low because titans have physical resistance. And then we also have to multiply the damage by
// two because the character shoots two arrows at a time.
EXPECT_EQ(pParty->pCharacters[2].GetBowItem()->special_enchantment, ITEM_ENCHANTMENT_TITAN_SLAYING);
EXPECT_EQ(pParty->pCharacters[2].GetRangedDamageString(), "41 - 45");
auto damageRange = hpsTape.reversed().adjacentDeltas().flattened().filtered([] (int damage) { return damage > 0; }).minMax();
EXPECT_EQ(damageRange, tape(1 * 2, (43 + 13) * 2));
}
18 changes: 18 additions & 0 deletions test/Testing/Game/ActorTapeRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,32 @@ TestTape<int> ActorTapeRecorder::countByBuff(ACTOR_BUFF_INDEX buff) {
});
}

TestMultiTape<int> ActorTapeRecorder::indicesByState(AIState state) {
return _controller->recordTape([state] {
AccessibleVector<int> result;
for (size_t i = 0; i < actors().size(); i++)
if (actors()[i].aiState == state)
result.push_back(i);
return result;
});
}

TestTape<int> ActorTapeRecorder::hp(int actorIndex) {
return custom(actorIndex, std::bind<int>(&Actor::currentHP, _1));
}

TestMultiTape<int> ActorTapeRecorder::hps(std::initializer_list<int> actorIndices) {
return custom(actorIndices, std::bind<int>(&Actor::currentHP, _1));
}

TestTape<AIState> ActorTapeRecorder::aiState(int actorIndex) {
return custom(actorIndex, std::bind(&Actor::aiState, _1));
}

TestMultiTape<AIState> ActorTapeRecorder::aiStates(std::initializer_list<int> actorIndices) {
return custom(actorIndices, std::bind(&Actor::aiState, _1));
}

TestTape<bool> ActorTapeRecorder::hasBuff(int actorIndex, ACTOR_BUFF_INDEX buff) {
return custom(actorIndex, [buff] (const Actor &actor) {
return actor.buffs[buff].Active();
Expand Down
16 changes: 16 additions & 0 deletions test/Testing/Game/ActorTapeRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,30 @@ class ActorTapeRecorder {
});
}

template<class Callback, class T = std::decay_t<std::invoke_result_t<Callback, const Actor &>>>
TestMultiTape<T> custom(std::initializer_list<int> actorIndices, Callback callback) {
return _controller->recordTape([actorIndices, callback = std::move(callback)] {
AccessibleVector<T> result;
for (int actorIndex : actorIndices)
result.push_back(callback(actors()[actorIndex]));
return result;
});
}

TestTape<int> countByState(AIState state);

TestTape<int> countByBuff(ACTOR_BUFF_INDEX buff);

TestMultiTape<int> indicesByState(AIState state);

TestTape<int> hp(int actorIndex);

TestMultiTape<int> hps(std::initializer_list<int> actorIndices);

TestTape<AIState> aiState(int actorIndex);

TestMultiTape<AIState> aiStates(std::initializer_list<int> actorIndices);

TestTape<bool> hasBuff(int actorIndex, ACTOR_BUFF_INDEX buff);

private:
Expand Down

0 comments on commit d533f43

Please sign in to comment.