• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 21600910629

02 Feb 2026 05:42PM UTC coverage: 50.721% (-0.03%) from 50.754%
21600910629

Pull #1683

github

web-flow
Merge b0763ef2d into dc0ce04f6
Pull Request #1683: Lua: Allow setting number of players, and placing HQs. Then fix the mission on map "The snake"

1 of 29 new or added lines in 7 files covered. (3.45%)

5 existing lines in 2 files now uncovered.

22796 of 44944 relevant lines covered (50.72%)

41333.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.57
/libs/s25main/lua/LuaPlayer.cpp
1
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "LuaPlayer.h"
6
#include "EventManager.h"
7
#include "Game.h"
8
#include "GamePlayer.h"
9
#include "ai/AIPlayer.h"
10
#include "buildings/nobBaseWarehouse.h"
11
#include "buildings/nobHQ.h"
12
#include "factories/BuildingFactory.h"
13
#include "helpers/EnumRange.h"
14
#include "helpers/toString.h"
15
#include "lua/LuaHelpers.h"
16
#include "lua/LuaInterfaceBase.h"
17
#include "notifications/BuildingNote.h"
18
#include "postSystem/PostMsgWithBuilding.h"
19
#include "world/GameWorld.h"
20
#include "world/TerritoryRegion.h"
21
#include "gameTypes/BuildingCount.h"
22
#include "gameData/BuildingConsts.h"
23
#include "s25util/Log.h"
24
#include <kaguya/kaguya.hpp>
25

26
const BasePlayerInfo& LuaPlayer::GetPlayer() const
33✔
27
{
28
    return player;
33✔
29
}
30

31
void LuaPlayer::Register(kaguya::State& state)
26✔
32
{
33
    LuaPlayerBase::Register(state);
26✔
34
    state["Player"].setClass(kaguya::UserdataMetatable<LuaPlayer, LuaPlayerBase>()
52✔
35
                               .addFunction("EnableBuilding", &LuaPlayer::EnableBuilding)
26✔
36
                               .addFunction("DisableBuilding", &LuaPlayer::DisableBuilding)
26✔
37
                               .addFunction("EnableAllBuildings", &LuaPlayer::EnableAllBuildings)
26✔
38
                               .addFunction("DisableAllBuildings", &LuaPlayer::DisableAllBuildings)
26✔
39
                               .addFunction("SetRestrictedArea", &LuaPlayer::SetRestrictedArea)
26✔
40
                               .addFunction("IsInRestrictedArea", &LuaPlayer::IsInRestrictedArea)
26✔
41
                               .addFunction("ClearResources", &LuaPlayer::ClearResources)
26✔
42
                               .addFunction("AddWares", &LuaPlayer::AddWares)
26✔
43
                               .addFunction("AddPeople", &LuaPlayer::AddPeople)
26✔
44
                               .addFunction("GetNumBuildings", &LuaPlayer::GetNumBuildings)
26✔
45
                               .addFunction("GetNumBuildingSites", &LuaPlayer::GetNumBuildingSites)
26✔
46
                               .addFunction("GetNumWares", &LuaPlayer::GetNumWares)
26✔
47
                               .addFunction("GetNumPeople", &LuaPlayer::GetNumPeople)
26✔
48
                               .addFunction("GetStatisticsValue", &LuaPlayer::GetStatisticsValue)
26✔
49
                               .addFunction("AIConstructionOrder", &LuaPlayer::AIConstructionOrder)
26✔
50
                               .addFunction("PlaceHQ", &LuaPlayer::PlaceHQ)
26✔
51
                               .addFunction("ModifyHQ", &LuaPlayer::ModifyHQ)
26✔
52
                               .addFunction("GetHQPos", &LuaPlayer::GetHQPos)
26✔
53
                               .addFunction("IsDefeated", &LuaPlayer::IsDefeated)
26✔
54
                               .addFunction("Surrender", &LuaPlayer::Surrender)
26✔
55
                               .addFunction("IsAlly", &LuaPlayer::IsAlly)
26✔
56
                               .addFunction("IsAttackable", &LuaPlayer::IsAttackable)
26✔
57
                               .addFunction("SuggestPact", &LuaPlayer::SuggestPact)
26✔
58
                               .addFunction("CancelPact", &LuaPlayer::CancelPact)
26✔
59
                               // Old names
60
                               .addFunction("GetBuildingCount", &LuaPlayer::GetNumBuildings)
26✔
61
                               .addFunction("GetBuildingSitesCount", &LuaPlayer::GetNumBuildingSites)
26✔
62
                               .addFunction("GetWareCount", &LuaPlayer::GetNumWares)
26✔
63
                               .addFunction("GetPeopleCount", &LuaPlayer::GetNumPeople));
26✔
64
}
26✔
65

66
void LuaPlayer::EnableBuilding(lua::SafeEnum<BuildingType> bld, bool notify)
3✔
67
{
68
    player.EnableBuilding(bld);
3✔
69
    if(notify)
3✔
70
    {
71
        player.SendPostMessage(
1✔
72
          std::make_unique<PostMsgWithBuilding>(player.GetGameWorld().GetEvMgr().GetCurrentGF(),
2✔
73
                                                std::string(_("New building type:")) + "\n" + _(BUILDING_NAMES[bld]),
2✔
74
                                                PostCategory::General, bld, player.nation));
2✔
75
    }
76
}
3✔
77

78
void LuaPlayer::DisableBuilding(lua::SafeEnum<BuildingType> bld)
3✔
79
{
80
    player.DisableBuilding(bld);
3✔
81
}
3✔
82

83
void LuaPlayer::EnableAllBuildings()
1✔
84
{
85
    for(const auto bld : helpers::enumRange<BuildingType>())
84✔
86
        player.EnableBuilding(bld);
40✔
87
}
1✔
88

89
void LuaPlayer::DisableAllBuildings()
1✔
90
{
91
    for(const auto bld : helpers::enumRange<BuildingType>())
84✔
92
        player.DisableBuilding(bld);
40✔
93
}
1✔
94

95
void LuaPlayer::SetRestrictedArea(kaguya::VariadicArgType inPoints)
11✔
96
{
97
    if(inPoints.size() == 0)
11✔
98
    {
99
        // Skip everything else if we only want to lift the restrictions
100
        player.GetRestrictedArea().clear();
1✔
101
        return;
1✔
102
    }
103

104
    std::vector<MapPoint> pts;
20✔
105
    // Index where the current polygon started
106
    int curPolyStart = -1;
10✔
107
    // Detect old-style separators for multi-polygon detection
108
    int lastNullPt = -1;
10✔
109
    // Do we have multiple polygons?
110
    bool isMultiPoly = false;
10✔
111
    for(auto it = inPoints.cbegin(); it != inPoints.cend(); ++it)
73✔
112
    {
113
        // Is this the separator between polygons?
114
        if(it->isNilref())
66✔
115
        {
116
            if(pts.empty()) // Start separator (old style)
12✔
117
                LOG.write("You don't need leading nils for SetRestrictedArea\n");
3✔
118
            else if(curPolyStart < 0) // We don't have a current polygon? Can only happen for multiple nils (old style)
9✔
119
                LOG.write("Duplicate nils found in SetRestrictedArea\n");
2✔
120
            else if(pts.size() < static_cast<unsigned>(curPolyStart) + 3)
7✔
121
                throw LuaExecutionError(std::string("Invalid polygon (less than 3 points) found at index ")
×
122
                                        + helpers::toString(std::distance(inPoints.cbegin(), it)));
×
123
            else if(pts[curPolyStart] != pts.back()) // Close polygon if not already done
7✔
124
                pts.push_back(pts[curPolyStart]);
5✔
125
            curPolyStart = -1;
12✔
126
        } else
127
        {
128
            // Do we start a new polygon?
129
            if(curPolyStart < 0)
54✔
130
            {
131
                if(!pts.empty())
14✔
132
                {
133
                    // We have multiple polygons -> Add separator
134
                    isMultiPoly = true;
4✔
135
                    if(pts.back() != MapPoint(0, 0))
4✔
136
                        pts.push_back(MapPoint(0, 0));
4✔
137
                }
138
                curPolyStart = pts.size();
14✔
139
            }
140
            int x = *it;
54✔
141
            ++it;
54✔
142
            int y = *it;
55✔
143
            if(x < 0 || y < 0)
53✔
144
                throw LuaExecutionError("Points must be positive");
2✔
145
            MapPoint pt(x, y);
51✔
146
            if(pt == MapPoint(0, 0))
51✔
147
            {
148
                // This might be the (old) separator if: We have a previous 0,0-pair, a valid polygon (>= 3 points) and
149
                // first pt after 0,0 matches last pt
150
                if(lastNullPt >= 0 && pts.size() - lastNullPt >= 3 && pts[lastNullPt + 1] == pts.back())
3✔
151
                    isMultiPoly = true;
2✔
152
                lastNullPt = pts.size();
3✔
153
            }
154
            pts.push_back(pt);
51✔
155
        }
156
    }
157
    if(isMultiPoly)
7✔
158
    {
159
        if(curPolyStart >= 0 && pts[curPolyStart] != pts.back()) // Close polygon if not already done
5✔
160
            pts.push_back(pts[curPolyStart]);
2✔
161
        if(pts.front() != MapPoint(0, 0))
5✔
162
            pts.insert(pts.begin(), MapPoint(0, 0));
4✔
163
        if(pts.back() != MapPoint(0, 0))
5✔
164
            pts.push_back(MapPoint(0, 0));
4✔
165
    } else if(pts.front() == pts.back())
2✔
166
        pts.pop_back();
1✔
167
    player.GetRestrictedArea() = pts;
7✔
168
}
169

170
bool LuaPlayer::IsInRestrictedArea(unsigned x, unsigned y) const
4✔
171
{
172
    const GameWorld& world = player.GetGameWorld();
4✔
173
    lua::assertTrue(x < world.GetWidth(), "x coordinate to large");
4✔
174
    lua::assertTrue(y < world.GetHeight(), "y coordinate to large");
4✔
175
    return TerritoryRegion::IsPointValid(world.GetSize(), player.GetRestrictedArea(), MapPoint(x, y));
4✔
176
}
177

178
void LuaPlayer::ClearResources()
4✔
179
{
180
    const std::list<nobBaseWarehouse*> warehouses = player.GetBuildingRegister().GetStorehouses();
8✔
181
    for(auto* warehouse : warehouses)
7✔
182
        warehouse->Clear();
3✔
183
}
4✔
184

185
bool LuaPlayer::AddWares(const std::map<lua::SafeEnum<GoodType>, unsigned>& wares)
35✔
186
{
187
    nobBaseWarehouse* warehouse = player.GetFirstWH();
35✔
188

189
    if(!warehouse)
35✔
190
        return false;
1✔
191

192
    Inventory goods;
34✔
193

194
    for(const auto& ware : wares)
70✔
195
    {
196
        goods.Add(ware.first, ware.second);
36✔
197
    }
198

199
    warehouse->AddGoods(goods, true);
34✔
200
    return true;
34✔
201
}
202

203
bool LuaPlayer::AddPeople(const std::map<lua::SafeEnum<Job>, unsigned>& people)
3✔
204
{
205
    nobBaseWarehouse* warehouse = player.GetFirstWH();
3✔
206

207
    if(!warehouse)
3✔
208
        return false;
1✔
209

210
    Inventory goods;
2✔
211

212
    for(const auto& it : people)
7✔
213
    {
214
        const Job job = it.first;
5✔
215
        if(job == Job::BoatCarrier)
5✔
216
        {
217
            goods.Add(Job::Helper, it.second);
1✔
218
            goods.Add(GoodType::Boat, it.second);
1✔
219
        } else
220
            goods.Add(job, it.second);
4✔
221
    }
222

223
    warehouse->AddGoods(goods, true);
2✔
224
    return true;
2✔
225
}
226

227
unsigned LuaPlayer::GetNumBuildings(lua::SafeEnum<BuildingType> bld) const
39✔
228
{
229
    return player.GetBuildingRegister().GetBuildingNums().buildings[bld];
39✔
230
}
231

232
unsigned LuaPlayer::GetNumBuildingSites(lua::SafeEnum<BuildingType> bld) const
3✔
233
{
234
    return player.GetBuildingRegister().GetBuildingNums().buildingSites[bld];
3✔
235
}
236

237
unsigned LuaPlayer::GetNumWares(lua::SafeEnum<GoodType> ware) const
68✔
238
{
239
    return player.GetInventory().goods[ware];
68✔
240
}
241

242
unsigned LuaPlayer::GetNumPeople(lua::SafeEnum<Job> job) const
37✔
243
{
244
    return player.GetInventory().people[job];
37✔
245
}
246

247
unsigned LuaPlayer::GetStatisticsValue(lua::SafeEnum<StatisticType> stat) const
10✔
248
{
249
    return player.GetStatisticCurrentValue(stat);
10✔
250
}
251

252
bool LuaPlayer::AIConstructionOrder(unsigned x, unsigned y, lua::SafeEnum<BuildingType> bld)
5✔
253
{
254
    // Only for actual AIs
255
    if(!player.isUsed() || player.isHuman())
5✔
256
        return false;
2✔
257
    GameWorld& world = player.GetGameWorld();
3✔
258
    lua::assertTrue(x < world.GetWidth(), "x coordinate to large");
5✔
259
    lua::assertTrue(y < world.GetHeight(), "y coordinate to large");
4✔
260
    world.GetNotifications().publish(BuildingNote(BuildingNote::LuaOrder, player.GetPlayerId(), MapPoint(x, y), bld));
1✔
261
    return true;
1✔
262
}
263

NEW
264
void LuaPlayer::PlaceHQ(MapCoord x, MapCoord y)
×
265
{
266
    // Ignore if there is an HQ set in the map file.
NEW
267
    if(player.GetHQPos().isValid())
×
NEW
268
        return;
×
269

NEW
270
    GameWorld& world = player.GetGameWorld();
×
NEW
271
    const MapPoint mp{x, y};
×
NEW
272
    constexpr auto checkExists = false;
×
NEW
273
    world.DestroyNO(mp, checkExists);
×
NEW
274
    BuildingFactory::CreateBuilding(world, BuildingType::Headquarters, mp, player.GetPlayerId(), player.nation);
×
275
}
276

277
void LuaPlayer::ModifyHQ(bool isTent)
3✔
278
{
279
    player.SetHQIsTent(isTent);
3✔
280
}
3✔
281

282
bool LuaPlayer::IsDefeated() const
2✔
283
{
284
    return player.IsDefeated();
2✔
285
}
286

287
void LuaPlayer::Surrender(bool destroyBlds)
2✔
288
{
289
    player.Surrender();
2✔
290
    if(destroyBlds)
2✔
291
        player.GetGameWorld().Armageddon(player.GetPlayerId());
1✔
292
}
2✔
293

294
std::pair<unsigned, unsigned> LuaPlayer::GetHQPos() const
2✔
295
{
296
    return std::pair<unsigned, unsigned>(player.GetHQPos().x, player.GetHQPos().y);
2✔
297
}
298

299
bool LuaPlayer::IsAlly(unsigned char otherPlayerId)
2✔
300
{
301
    return player.IsAlly(otherPlayerId);
2✔
302
}
303

304
bool LuaPlayer::IsAttackable(unsigned char otherPlayerId)
3✔
305
{
306
    return player.IsAttackable(otherPlayerId);
3✔
307
}
308

309
void LuaPlayer::SuggestPact(unsigned char otherPlayerId, const lua::SafeEnum<PactType> pt, const unsigned duration)
1✔
310
{
311
    AIPlayer* ai = game.GetAIPlayer(player.GetPlayerId());
1✔
312
    if(ai != nullptr)
1✔
313
        ai->getAIInterface().SuggestPact(otherPlayerId, pt, duration);
1✔
314
}
1✔
315

316
void LuaPlayer::CancelPact(const lua::SafeEnum<PactType> pt, unsigned char otherPlayerId)
1✔
317
{
318
    AIPlayer* ai = game.GetAIPlayer(player.GetPlayerId());
1✔
319
    if(ai != nullptr)
1✔
320
        ai->getAIInterface().CancelPact(pt, otherPlayerId);
1✔
321
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc