Skip to content

Commit

Permalink
Merge pull request #639 from zzam/burningBuilding
Browse files Browse the repository at this point in the history
Fix glitch of burning building animation
  • Loading branch information
Jarod42 committed Mar 22, 2024
2 parents 8bc3a7b + e33ddad commit 6aa6d81
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 10 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ set(stratagus_tests_SRCS
tests/main.cpp
tests/stratagus/test_depend.cpp
tests/stratagus/test_luacallback.cpp
tests/stratagus/test_missile_fire.cpp
tests/stratagus/test_trigger.cpp
tests/stratagus/test_util.cpp
tests/network/test_net_lowlevel.cpp
Expand Down
4 changes: 3 additions & 1 deletion src/include/missile.h
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ class BurningBuildingFrame
-- Variables
----------------------------------------------------------------------------*/

extern std::vector<std::unique_ptr<BurningBuildingFrame>> BurningBuildingFrames; /// Burning building frames
extern std::vector<BurningBuildingFrame> BurningBuildingFrames; /// Burning building frames

/*----------------------------------------------------------------------------
-- Functions
Expand Down Expand Up @@ -630,6 +630,8 @@ extern void InitMissiles();
/// Clean missiles
extern void CleanMissiles();

extern bool IsBurningBuildingFramesValid();

extern void FreeBurningBuildingFrames();

//@}
Expand Down
36 changes: 36 additions & 0 deletions src/include/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,12 +276,39 @@ namespace ranges
return std::min_element(std::begin(range), std::end(range), cmp);
}

template <typename I, typename S, typename Pred, typename Proj = identity>
auto partition_point(I first, S last, Pred pred, Proj &&proj = {})
{
return std::partition_point(first, last,
[&](const auto &elem) {
return std::invoke(pred, std::invoke(proj, elem));
});
}

template <typename I, typename S, typename Value, typename Comparer = std::less<>, typename Proj = identity>
auto lower_bound(I first, S last, const Value &value, Comparer &&comparer = {}, Proj &&proj = {})
{
return std::lower_bound(first, last, value,
[&](const auto &lhs, const auto &rhs) {
return std::invoke(comparer, std::invoke(proj, lhs), rhs);
});
}

template <typename Range, typename Value>
auto lower_bound(Range &range, const Value &value)
{
return std::lower_bound(std::begin(range), std::end(range), value);
}

template <typename I, typename S, typename Value, typename Comparer = std::less<>, typename Proj = identity>
auto upper_bound(I first, S last, const Value &value, Comparer &&comparer = {}, Proj &&proj = {})
{
return std::upper_bound(first, last, value,
[&](const auto &lhs, const auto &rhs) {
return std::invoke(comparer, lhs, std::invoke(proj, rhs));
});
}

template <typename Range, typename Value, typename Comparer = std::less<>>
auto upper_bound(Range &range, const Value &value, Comparer &&comparer = {})
{
Expand Down Expand Up @@ -323,6 +350,15 @@ namespace ranges
std::sort(std::begin(range), std::end(range), std::forward<Comparer>(comparer));
}

template <typename Range, typename Comparer = std::less<>, typename Proj = identity>
bool is_sorted(const Range &range, Comparer &&comparer = {}, Proj &&proj = {})
{
return std::is_sorted(std::begin(range), std::end(range),
[&](const auto &lhs, const auto &rhs) {
return std::invoke(comparer, std::invoke(proj, lhs), std::invoke(proj, rhs));
});
}

}

//@}
Expand Down
36 changes: 31 additions & 5 deletions src/missile/missile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ static std::vector<std::unique_ptr<Missile>> LocalMissiles; /// all local mi
using MissileTypeMap = std::map<std::string, std::unique_ptr<MissileType>, std::less<>>;
static MissileTypeMap MissileTypes;

std::vector<std::unique_ptr<BurningBuildingFrame>> BurningBuildingFrames; /// Burning building frames
std::vector<BurningBuildingFrame> BurningBuildingFrames; /// Burning building frames

extern std::unique_ptr<INumberDesc> Damage; /// Damage calculation for missile.

Expand Down Expand Up @@ -1172,10 +1172,15 @@ int ViewPointDistanceToMissile(const Missile &missile)
*/
MissileType *MissileBurningBuilding(int percent)
{
for (auto &frame : BurningBuildingFrames) {
if (percent > frame->Percent) {
return frame->Missile;
}
const auto it = ranges::upper_bound(
std::begin(BurningBuildingFrames),
std::end(BurningBuildingFrames),
percent,
std::less<>{},
&BurningBuildingFrame::Percent
);
if (it != std::begin(BurningBuildingFrames)) {
return std::prev(it)->Missile;
}
return nullptr;
}
Expand Down Expand Up @@ -1297,6 +1302,27 @@ void CleanMissiles()
LocalMissiles.clear();
}

template <typename Range>
bool IsPercentIncreasingValid(const Range &range)
{
bool ret = true;
if (!range.empty())
{
ret = range.front().Percent >= 0
&& range.back().Percent <= 100;
}

return ret && ranges::is_sorted(
range,
std::less<>{},
[](const auto& e) { return e.Percent; });
}

bool IsBurningBuildingFramesValid()
{
return IsPercentIncreasingValid(BurningBuildingFrames);
}

void FreeBurningBuildingFrames()
{
BurningBuildingFrames.clear();
Expand Down
12 changes: 8 additions & 4 deletions src/missile/script_missile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,24 +339,28 @@ static int CclDefineBurningBuilding(lua_State *l)
BurningBuildingFrames.clear();

const int args = lua_gettop(l);
BurningBuildingFrames.resize(args);
for (int j = 0; j < args; ++j) {
if (!lua_istable(l, j + 1)) {
LuaError(l, "incorrect argument");
}
auto ptr = std::make_unique<BurningBuildingFrame>();
auto &frame = BurningBuildingFrames[j];
const int subargs = lua_rawlen(l, j + 1);

for (int k = 0; k < subargs; ++k) {
const std::string_view value = LuaToString(l, j + 1, k + 1);
++k;

if (value == "percent") {
ptr->Percent = LuaToNumber(l, j + 1, k + 1);
frame.Percent = LuaToNumber(l, j + 1, k + 1);
} else if (value == "missile") {
ptr->Missile = &MissileTypeByIdent(LuaToString(l, j + 1, k + 1));
frame.Missile = &MissileTypeByIdent(LuaToString(l, j + 1, k + 1));
}
}
BurningBuildingFrames.insert(BurningBuildingFrames.begin(), std::move(ptr));
}
if (!IsBurningBuildingFramesValid())
{
LuaError(l, "Percents of BurningBuilding not valid (percents out of range or not sorted)");
}
return 0;
}
Expand Down
209 changes: 209 additions & 0 deletions tests/stratagus/test_missile_fire.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// _________ __ __
// / _____// |_____________ _/ |______ ____ __ __ ______
// \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/
// / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ |
// /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ >
// \/ \/ \//_____/ \/
// ______________________ ______________________
// T H E W A R B E G I N S
// Stratagus - A free fantasy real time strategy game engine
//
/**@name test_missile_fire.cpp - The test file for missile_fire.cpp / missile.cpp. */
//
// (c) Copyright 2024 by Matthias Schwarzott
//
// 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; only version 2 of the License.
//
// 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, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//

#include <doctest.h>

#include "stratagus.h"
#include "missile.h"

namespace {
MissileType* dummyMissile(uintptr_t m)
{
return reinterpret_cast<MissileType*>(0x10000 + m);
}

void setFrames(const std::vector<std::pair<int, MissileType*>>& frames)
{
const std::size_t size = frames.size();
BurningBuildingFrames.resize(size);
for (int i = 0; i < size; ++i) {
BurningBuildingFrames[i].Percent = frames[i].first;
BurningBuildingFrames[i].Missile = frames[i].second;
}
}

} // anonymous namespace

TEST_CASE("missile_fire")
{
MissileType *m0 = dummyMissile(0);
MissileType *m1 = dummyMissile(1);
MissileType *m2 = dummyMissile(2);
MissileType *m3 = dummyMissile(3);
MissileType *m4 = dummyMissile(4);
MissileType *m5 = dummyMissile(5);
MissileType *m6 = dummyMissile(6);
MissileType *m7 = dummyMissile(7);
MissileType *m8 = dummyMissile(8);
MissileType *m9 = dummyMissile(9);

BurningBuildingFrames.clear();

SUBCASE("size=0") {
setFrames({});
CHECK(IsBurningBuildingFramesValid());

CHECK(MissileBurningBuilding( 0) == nullptr);
}

SUBCASE("size=1") {
setFrames({
{ 0, m0},
});
CHECK(IsBurningBuildingFramesValid());

CHECK(MissileBurningBuilding( 0) == m0);
CHECK(MissileBurningBuilding( 1) == m0);
CHECK(MissileBurningBuilding(100) == m0);
}

SUBCASE("large steps") {
setFrames({
{ 0, m0},
{ 50, m1},
});
CHECK(IsBurningBuildingFramesValid());

SUBCASE("normal") {
CHECK(MissileBurningBuilding( 0) == m0);
CHECK(MissileBurningBuilding( 1) == m0);
CHECK(MissileBurningBuilding( 49) == m0);
CHECK(MissileBurningBuilding( 50) == m1);
CHECK(MissileBurningBuilding( 51) == m1);
CHECK(MissileBurningBuilding(100) == m1);
}

SUBCASE("out of bounds") {
CHECK(MissileBurningBuilding( -1) == nullptr);
CHECK(MissileBurningBuilding(101) == m1);
}
}

SUBCASE("partially dense")
{
setFrames({
{ 0, m0},
{ 1, m1},
{ 2, m2},
{ 65, m3},
{ 66, m4},
{ 67, m5},
{ 99, m6},
{100, m7},
});
CHECK(IsBurningBuildingFramesValid());

CHECK(MissileBurningBuilding( 0) == m0);
CHECK(MissileBurningBuilding( 1) == m1);
CHECK(MissileBurningBuilding( 2) == m2);
CHECK(MissileBurningBuilding( 3) == m2);

CHECK(MissileBurningBuilding( 50) == m2);

CHECK(MissileBurningBuilding( 64) == m2);
CHECK(MissileBurningBuilding( 65) == m3);
CHECK(MissileBurningBuilding( 66) == m4);
CHECK(MissileBurningBuilding( 67) == m5);
CHECK(MissileBurningBuilding( 68) == m5);

CHECK(MissileBurningBuilding( 98) == m5);
CHECK(MissileBurningBuilding( 99) == m6);
CHECK(MissileBurningBuilding(100) == m7);
}

SUBCASE("repeated values and null")
{
setFrames({
{ 0, m8},
{ 80, nullptr},
{ 90, m8},
});
CHECK(IsBurningBuildingFramesValid());

CHECK(MissileBurningBuilding( 79) == m8);
CHECK(MissileBurningBuilding( 80) == nullptr);
CHECK(MissileBurningBuilding( 89) == nullptr);
CHECK(MissileBurningBuilding( 90) == m8);
}

SUBCASE("first key larger") {
setFrames({
{ 10, m5},
});
CHECK(IsBurningBuildingFramesValid());

CHECK(MissileBurningBuilding( 9) == nullptr);
CHECK(MissileBurningBuilding(10) == m5);
}

SUBCASE("duplicate keys")
{
setFrames({
{ 0, m0},
{ 20, m1},
{ 20, m2},
{ 60, m3},
});
CHECK(IsBurningBuildingFramesValid());

CHECK(MissileBurningBuilding(19) == m0);
CHECK(MissileBurningBuilding(20) == m2);
CHECK(MissileBurningBuilding(21) == m2);
}

SUBCASE("wrong key order") {
setFrames({
{ 75, m9},
{ 50, m8},
{ 0, m7},
});

CHECK_FALSE(IsBurningBuildingFramesValid());
}

SUBCASE("too small key value") {
setFrames({
{ -1, m7},
{100, m8},
});

CHECK_FALSE(IsBurningBuildingFramesValid());
}

SUBCASE("too large key value") {
setFrames({
{ 0, m7},
{101, m8},
});

CHECK_FALSE(IsBurningBuildingFramesValid());
}

BurningBuildingFrames.clear();
}

0 comments on commit 6aa6d81

Please sign in to comment.