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

Return-To-The-Roots / s25client / 22108417915

17 Feb 2026 05:19PM UTC coverage: 50.347% (-0.5%) from 50.809%
22108417915

Pull #1720

github

web-flow
Merge cdf86094c into 6e122731f
Pull Request #1720: Add leather addon

281 of 1064 new or added lines in 65 files covered. (26.41%)

42 existing lines in 25 files now uncovered.

23023 of 45729 relevant lines covered (50.35%)

43595.28 hits per line

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

49.67
/libs/s25main/nodeObjs/noFighting.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 "noFighting.h"
6
#include "EventManager.h"
7
#include "GamePlayer.h"
8
#include "GlobalGameSettings.h"
9
#include "Loader.h"
10
#include "SerializedGameData.h"
11
#include "SoundManager.h"
12
#include "addons/const_addons.h"
13
#include "figures/nofActiveSoldier.h"
14
#include "network/GameClient.h"
15
#include "noSkeleton.h"
16
#include "ogl/glArchivItem_Bitmap_Player.h"
17
#include "ogl/glSmartBitmap.h"
18
#include "random/Random.h"
19
#include "world/GameWorld.h"
20
#include "gameData/MilitaryConsts.h"
21

22
noFighting::noFighting(nofActiveSoldier& soldier1, nofActiveSoldier& soldier2) : noBase(NodalObjectType::Fighting)
21✔
23
{
24
    RTTR_Assert(soldier1.GetPlayer() != soldier2.GetPlayer());
21✔
25
    const MapPoint pos = soldier1.GetPos();
21✔
26

27
    soldiers[0] = world->RemoveFigure(pos, soldier1);
21✔
28
    soldiers[1] = world->RemoveFigure(pos, soldier2);
21✔
29
    turn = 2;
21✔
30
    defending_animation = 0;
21✔
31
    player_won = 0xFF;
21✔
32

33
    // Beginn-Event Anmelden (Soldaten gehen auf ihre Seiten)
34
    current_ev = GetEvMgr().AddEvent(this, 15);
21✔
35

36
    // anderen Leute, die auf diesem Punkt zulaufen, stoppen
37
    world->StopOnRoads(pos);
21✔
38

39
    // Sichtradius behalten
40
    world->MakeVisibleAroundPoint(pos, VISUALRANGE_SOLDIER, soldier1.GetPlayer());
21✔
41
    world->MakeVisibleAroundPoint(pos, VISUALRANGE_SOLDIER, soldier2.GetPlayer());
21✔
42
}
21✔
43

44
noFighting::~noFighting() = default;
42✔
45

46
void noFighting::Serialize(SerializedGameData& sgd) const
×
47
{
48
    noBase::Serialize(sgd);
×
49

50
    sgd.PushUnsignedChar(turn);
×
51
    sgd.PushUnsignedChar(defending_animation);
×
52
    sgd.PushEvent(current_ev);
×
53
    sgd.PushUnsignedChar(player_won);
×
54

55
    for(const auto& soldier : soldiers)
×
56
        sgd.PushObject(soldier);
×
57
}
×
58

59
noFighting::noFighting(SerializedGameData& sgd, const unsigned obj_id)
×
60
    : noBase(sgd, obj_id), turn(sgd.PopUnsignedChar()), defending_animation(sgd.PopUnsignedChar()),
×
61
      current_ev(sgd.PopEvent()), player_won(sgd.PopUnsignedChar())
×
62

63
{
64
    for(auto& soldier : soldiers)
×
65
        soldier.reset(sgd.PopObject<nofActiveSoldier>());
×
66
}
×
67

68
void noFighting::Destroy()
10✔
69
{
70
    RTTR_Assert(!soldiers[0]);
10✔
71
    RTTR_Assert(!soldiers[1]);
10✔
72
    noBase::Destroy();
10✔
73
}
10✔
74

75
void noFighting::Draw(DrawPoint drawPt)
×
76
{
77
    switch(turn)
×
78
    {
79
        case 3:
×
80
        case 4:
81
        {
82
            const unsigned curAnimFrame = GAMECLIENT.Interpolate(16, current_ev);
×
83
            const auto& soldier = *soldiers[turn - 3];
×
84
            const GamePlayer& owner = world->GetPlayer(soldier.GetPlayer());
×
85

86
            // Sterben des einen Soldatens (letzte Phase)
87

88
            if(curAnimFrame < 4)
×
89
            {
90
                // Noch kurz dastehen und warten, bis man stirbt
91
                LOADER.fight_cache[owner.nation][soldier.GetRank()][turn - 3].defending[0][0].drawForPlayer(
×
92
                  drawPt, owner.color);
×
93
            } else
94
            {
95
                // Sich in Luft auflösen
96
                if(turn == 3)
×
97
                    drawPt.x -= 12;
×
98
                else
99
                    drawPt.x += 12;
×
100
                LOADER.GetPlayerImage("rom_bobs", 903 + curAnimFrame - 4)->DrawFull(drawPt, COLOR_WHITE, owner.color);
×
101
            }
NEW
102
            if(soldiers[turn - 3]->HasArmor())
×
NEW
103
                soldiers[turn - 3]->DrawArmor(drawPt);
×
104

105
            // Sterbesound abspielen
106
            if(curAnimFrame == 6)
×
107
                world->GetSoundMgr().playNOSound(104, *this, 2);
×
108
        }
109
        break;
×
110
        case 2:
×
111
        {
112
            // Erste Phase des Kampfes, die Soldaten gehen jeweils nach links bzw. rechts
113
            auto x_diff = int(GAMECLIENT.Interpolate(12, current_ev));
×
114
            drawPt.x -= x_diff;
×
115
            for(unsigned i : {0, 1})
×
116
            {
117
                const GamePlayer& owner = world->GetPlayer(soldiers[i]->GetPlayer());
×
118
                glSmartBitmap& bmp = LOADER.getBobSprite(owner.nation, soldiers[i]->GetJobType(),
×
119
                                                         (i == 0) ? Direction::West : Direction::East,
120
                                                         GAMECLIENT.Interpolate(8, current_ev));
×
121
                bmp.draw(drawPt, COLOR_WHITE, owner.color);
×
NEW
122
                if(soldiers[i]->HasArmor())
×
NEW
123
                    soldiers[i]->DrawArmor(drawPt);
×
UNCOV
124
                drawPt.x += 2 * x_diff;
×
125
            }
126
        }
127
        break;
×
128
        default:
×
129
        {
130
            // Kampf
131
            // Aktueller Animationsframe
132
            const unsigned curAnimFrame = GAMECLIENT.Interpolate(8, current_ev);
×
133

134
            for(unsigned i : {0, 1})
×
135
            {
136
                const auto& soldier = *soldiers[i];
×
137
                const GamePlayer& owner = world->GetPlayer(soldier.GetPlayer());
×
138
                auto& fightAnim = LOADER.fight_cache[owner.nation][soldier.GetRank()][i];
×
139

140
                // Ist der Soldat mit Angreifen dran?
141
                if(turn == i)
×
142
                {
143
                    // Angreifen
144
                    fightAnim.attacking[curAnimFrame].drawForPlayer(drawPt, owner.color);
×
145
                } else
146
                {
147
                    // Verteidigen
148
                    if(defending_animation < 3)
×
149
                    {
150
                        // Verteidigungsanimation
151
                        fightAnim.defending[defending_animation][curAnimFrame].drawForPlayer(drawPt, owner.color);
×
152

153
                        // Wenn schwache Soldaten Schild hinhalten (Ani 0 und 1) oder stärkere sich mit den Schwertern
154
                        // schützen (Ani 0) dann Schwert-aneinanderklirr-Sound abspielen
155
                        if(curAnimFrame == 5
×
156
                           && ((soldier.GetRank() < 2 && defending_animation < 2)
×
157
                               || (soldier.GetRank() > 1 && defending_animation == 0)))
×
158
                            world->GetSoundMgr().playNOSound(101, *this, 1);
×
159

160
                    } else
161
                    {
162
                        // Getroffen-Animation (weißes Aufblinken)
163
                        if(curAnimFrame == HIT_MOMENT[soldiers[1 - i]->GetRank()])
×
164
                        {
165
                            // weiß aufblinken
166
                            fightAnim.hit.drawForPlayer(drawPt, owner.color);
×
167

168
                            // Treffersound
169
                            world->GetSoundMgr().playNOSound(105, *this, 1);
×
170
                        } else
171
                            // normal dastehen
172
                            fightAnim.defending[0][0].drawForPlayer(drawPt, owner.color);
×
173
                    }
174
                }
NEW
175
                if(soldiers[i]->HasArmor())
×
NEW
176
                    soldiers[i]->DrawArmor(drawPt + DrawPoint(i == 0 ? -12 : 12, 0));
×
177
            }
178

179
            // Angriffssound
180
            if(curAnimFrame == 3)
×
181
                world->GetSoundMgr().playNOSound(103, *this, 0);
×
182
        }
183
        break;
×
184
    }
185
}
×
186

187
void noFighting::HandleEvent(const unsigned id)
226✔
188
{
189
    // Normales Ablaufevent?
190
    if(id == 0)
226✔
191
    {
192
        switch(turn)
226✔
193
        {
194
            case 2:
19✔
195
            {
196
                // Der Kampf hat gerade begonnen
197

198
                // "Auslosen", wer als erstes dran ist mit Angreifen
199
                turn = static_cast<unsigned char>(RANDOM_RAND(2));
19✔
200
                // anfangen anzugreifen
201
                StartAttack();
19✔
202
            }
203
                return;
19✔
204
            case 0:
197✔
205
            case 1:
206
            {
207
                // Sounds löschen von der letzten Kampfphase
208
                world->GetSoundMgr().stopSounds(*this);
197✔
209

210
                // Wurde der eine getroffen?
211
                if(defending_animation == 3)
197✔
212
                {
213
                    soldiers[1 - turn]->TakeHit();
68✔
214
                    if(soldiers[1 - turn]->GetHitpoints() == 0)
68✔
215
                    {
216
                        // Besitzer merken für die Sichtbarkeiten am Ende dann
217
                        player_won = soldiers[turn]->GetPlayer();
18✔
218
                        // Soldat Bescheid sagen, dass er stirbt
219
                        soldiers[1 - turn]->LostFighting();
18✔
220
                        // Anderen Soldaten auf die Karte wieder setzen, Bescheid sagen, er kann wieder loslaufen
221
                        const MapPoint pos = soldiers[turn]->GetPos();
18✔
222
                        auto& winningSoldier = world->AddFigure(pos, std::move(soldiers[turn]));
18✔
223
                        // Hitpoints sind 0 --> Soldat ist tot, Kampf beendet, turn = 3+welche Soldat stirbt
224
                        // Do this before calling WonFighting so this fight doesn't block the soldier looking for a new
225
                        // fight spot
226
                        turn = 3 + (1 - turn);
18✔
227
                        winningSoldier.WonFighting();
18✔
228
                        // Event zum Sterben des einen Soldaten anmelden
229
                        current_ev = GetEvMgr().AddEvent(this, 30);
18✔
230
                        // Umstehenden Figuren Bescheid Bescheid sagen
231
                        world->RoadNodeAvailable(pos);
18✔
232

233
                        // In die Statistik eintragen
234
                        world->GetPlayer(player_won).ChangeStatisticValue(StatisticType::Vanquished, 1);
18✔
235
                        return;
18✔
236
                    }
237
                }
238

239
                turn = 1 - turn;
179✔
240
                StartAttack();
179✔
241
            }
242
                return;
179✔
243
            case 3:
10✔
244
            case 4:
245
            {
246
                unsigned player_lost = turn - 3;
10✔
247
                MapPoint pt = soldiers[player_lost]->GetPos();
10✔
248

249
                // Sounds löschen vom Sterben
250
                world->GetSoundMgr().stopSounds(*this);
10✔
251

252
                // Kampf ist endgültig beendet
253
                GetEvMgr().AddToKillList(world->RemoveFigure(pt, *this));
10✔
254

255
                // Wenn da nix war bzw. nur ein Verzierungsobjekt, kommt nun ein Skelett hin
256
                NodalObjectType noType = world->GetNO(pt)->GetType();
10✔
257
                if(noType == NodalObjectType::Nothing || noType == NodalObjectType::Environment)
10✔
258
                {
259
                    world->DestroyNO(pt, false);
1✔
260
                    world->SetNO(pt, new noSkeleton(pt));
1✔
261
                }
262

263
                // Sichtradius ausblenden am Ende des Kampfes, an jeweiligen Soldaten dann übergeben, welcher überlebt
264
                // hat
265
                world->RecalcVisibilitiesAroundPoint(pt, VISUALRANGE_SOLDIER, soldiers[player_lost]->GetPlayer(),
10✔
266
                                                     nullptr);
267
                world->RecalcVisibilitiesAroundPoint(pt, VISUALRANGE_SOLDIER, player_won, nullptr);
10✔
268

269
                // Soldaten endgültig umbringen
270
                world->GetPlayer(soldiers[player_lost]->GetPlayer())
10✔
271
                  .DecreaseInventoryJob(soldiers[player_lost]->GetJobType(), 1);
10✔
272
                soldiers[player_lost]->Destroy();
10✔
273
                soldiers[player_lost].reset();
10✔
274
            }
275
            break;
10✔
276
        }
277
    } else
278
        RTTR_Assert(false);
×
279
}
280

281
void noFighting::StartAttack()
198✔
282
{
283
    // "Auswürfeln", ob der Angreifer (also der, der gerade den Angriff vollzieht) trifft oder ob sich der andere
284
    // erfolgreich verteidigt
285

286
    std::array<unsigned char, 2> results;
287
    for(unsigned i = 0; i < 2; ++i)
594✔
288
    {
289
        switch(world->GetGGS().getSelection(AddonId::ADJUST_MILITARY_STRENGTH))
396✔
290
        {
291
            case 0: // Maximale Stärke
×
292
                results[i] = RANDOM_RAND(soldiers[i]->GetRank() + 6);
×
293
                break;
×
294
            case 1: // Mittlere Stärke
396✔
295
            default: results[i] = RANDOM_RAND(soldiers[i]->GetRank() + 10); break;
396✔
296
            case 2: // Minimale Stärke
×
297
                results[i] = RANDOM_RAND(10);
×
298
                break;
×
299
        }
300
    }
301

302
    if((turn == 0 && results[0] > results[1]) || (turn == 1 && results[1] > results[0]))
198✔
303
        // Der Angreifer hat diesen Zug gewonnen
304
        defending_animation = 3;
68✔
305
    else
306
        // Der Verteidiger hat diesen Zug gewonnen, zufällige Verteidigungsanimation
307
        defending_animation = static_cast<unsigned char>(RANDOM_RAND(3));
130✔
308

309
    // Entsprechendes Event anmelden
310
    current_ev = GetEvMgr().AddEvent(this, 15);
198✔
311
}
198✔
312

313
bool noFighting::IsActive() const
256✔
314
{
315
    return turn < 3;
256✔
316
}
317

318
bool noFighting::IsSoldierOfPlayer(const unsigned char player) const
8✔
319
{
320
    for(const std::unique_ptr<nofActiveSoldier>& soldier : soldiers)
24✔
321
    {
322
        if(soldier && soldier->GetPlayer() == player)
16✔
323
            return true;
×
324
    }
325

326
    // Der Spieler der gewonnen hat und schon wieder gegangen ist (taucht dann nicht bei den ersten beiden mit
327
    // auf, wenn der Kampf beendet ist)
328
    return player_won == player;
8✔
329
}
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