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

Return-To-The-Roots / s25client / 22044569181

15 Feb 2026 10:50PM UTC coverage: 50.34% (-0.5%) from 50.826%
22044569181

Pull #1720

github

web-flow
Merge 4dbe54b70 into 6db06730b
Pull Request #1720: Add leather addon

274 of 1055 new or added lines in 65 files covered. (25.97%)

286 existing lines in 28 files now uncovered.

23017 of 45723 relevant lines covered (50.34%)

43559.46 hits per line

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

35.79
/libs/s25main/nodeObjs/noAnimal.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 "noAnimal.h"
6
#include "EventManager.h"
7
#include "LeatherLoader.h"
8
#include "Loader.h"
9
#include "SerializedGameData.h"
10
#include "SoundManager.h"
11
#include "drivers/VideoDriverWrapper.h"
12
#include "figures/nofHunter.h"
13
#include "figures/nofSkinner.h"
14
#include "helpers/random.h"
15
#include "network/GameClient.h"
16
#include "ogl/SoundEffectItem.h"
17
#include "ogl/glSmartBitmap.h"
18
#include "random/Random.h"
19
#include "world/GameWorld.h"
20
#include "gameData/GameConsts.h"
21
#include "gameData/TerrainDesc.h"
22
#include "s25util/colors.h"
23

24
namespace {
25
auto& getAnimalRng()
×
26
{
27
    static auto soundRng = helpers::getRandomGenerator();
×
28
    return soundRng;
×
29
}
30
} // namespace
31

32
noAnimal::noAnimal(const Species species, const MapPoint pos)
431✔
33
    : noMovable(NodalObjectType::Animal, pos), species(species), state(State::Walking), pause_way(5 + RANDOM_RAND(15)),
431✔
34
      hunter(nullptr), skinner(nullptr), sound_moment(0)
431✔
35
{}
431✔
36

37
void noAnimal::Serialize(SerializedGameData& sgd) const
2✔
38
{
39
    noMovable::Serialize(sgd);
2✔
40

41
    sgd.PushEnum<uint8_t>(species);
2✔
42
    sgd.PushEnum<uint8_t>(state);
2✔
43
    sgd.PushUnsignedShort(pause_way);
2✔
44
    sgd.PushObject(hunter, true);
2✔
45
    sgd.PushObject(skinner, true);
2✔
46
}
2✔
47

48
noAnimal::noAnimal(SerializedGameData& sgd, const unsigned obj_id)
1✔
49
    : noMovable(sgd, obj_id), species(sgd.Pop<Species>()), state(sgd.Pop<State>()), pause_way(sgd.PopUnsignedShort()),
4✔
50
      hunter(sgd.PopObject<nofHunter>(GO_Type::NofHunter)), skinner(nullptr), sound_moment(0)
1✔
51
{
52
    if(sgd.GetGameDataVersion() >= 12)
1✔
53
        skinner = sgd.PopObject<nofSkinner>(GO_Type::NofSkinner);
1✔
54
}
1✔
55

56
void noAnimal::StartLiving()
430✔
57
{
58
    // anfangen zu laufen
59
    StandardWalking();
430✔
60
}
430✔
61

62
void noAnimal::Draw(DrawPoint drawPt)
×
63
{
64
    // Tier zeichnen
65

66
    switch(state)
×
67
    {
68
        default: break;
×
69
        case State::WalkingUntilWaitingForHunter:
×
70
        case State::Walking:
71
        {
72
            // Laufend (bzw. Ente schwimmend zeichnen)
73

74
            // Interpolieren zwischen beiden Knotenpunkten
75
            drawPt += CalcWalkingRelative();
×
76

77
            unsigned ani_step = GAMECLIENT.Interpolate(ASCENT_ANIMATION_STEPS[GetAscent()], current_ev)
×
78
                                % ANIMALCONSTS[species].animation_steps;
×
79

80
            // Zeichnen
81
            LOADER.getAnimalSprite(species, GetCurMoveDir(), ani_step).draw(drawPt);
×
82

83
            // Bei Enten und Schafen: Soll ein Sound gespielt werden?
84
            if(species == Species::Duck || species == Species::Sheep)
×
85
            {
86
                const unsigned now = VIDEODRIVER.GetTickCount();
×
87
                // Wurde der Soundzeitpunkt schon überschritten?
88
                if(now > sound_moment)
×
89
                {
90
                    // Play the sound if we unless we missed the timeframe by at least 1s
91
                    if((now < sound_moment + 1000) && !GAMECLIENT.IsPaused())
×
92
                        world->GetSoundMgr().playAnimalSound((species == Species::Sheep) ? 94 : 95);
×
93

94
                    // Calc new sound time
95
                    sound_moment = now + helpers::randomValue(getAnimalRng(), 8000, 13000);
×
96
                }
97
            }
98
        }
99
        break;
×
100
        case State::WaitingForHunter:
×
101
        case State::Paused:
102
        {
103
            // Stehend zeichnen
104
            LOADER.getAnimalSprite(species, GetCurMoveDir(), 0).draw(drawPt);
×
105
        }
106
        break;
×
107
        case State::Dead:
×
108
        {
109
            if(!LOADER.getDeadAnimalSprite(species).empty())
×
110
            {
111
                LOADER.getDeadAnimalSprite(species).draw(drawPt);
×
112
            }
113
        }
114
        break;
×
115
        case State::Disappearing:
×
116
        {
117
            // Alpha-Wert ausrechnen
118
            unsigned char alpha = 0xFF - GAMECLIENT.Interpolate(0xFF, current_ev);
×
119

120
            // Gibts ein Leichenbild?
121
            if(!LOADER.getDeadAnimalSprite(species).empty())
×
122
            {
123
                LOADER.getDeadAnimalSprite(species).draw(drawPt, SetAlpha(COLOR_WHITE, alpha));
×
124
            } else
125
            {
126
                // Stehend zeichnen
127
                LOADER.getAnimalSprite(species, GetCurMoveDir(), 0).draw(drawPt, SetAlpha(COLOR_WHITE, alpha));
×
128
            }
129
        }
130
        break;
×
131
    }
132
}
×
133

134
/*
135
    An animal can be hunted by the hunter and skinned by the skinner at the same time.
136
    We have the following cases:
137
    - Both do not reach the dead animal in time. It disappears and the noAnimal
138
      class is responsible for removing and deleting itself afterwards.
139
    - Both reach the dead animal in time. However finishes last is responsible for removing
140
      and deleting the dead animal (see nofSkinner::HandleStateSkinningCarcass and
141
      nofHunter::HandleStateEviscerating). No disappearing of the animal.
142
    - Either doesn't arrive soon enough. If the hunter was first the skin remains there
143
      and if the skinner was first the meat remains there. The animal disappears and the noAnimal
144
      class is responsible for removing and deleting itself afterwards.
145
*/
UNCOV
146
void noAnimal::HandleEvent(const unsigned id)
×
147
{
148
    current_ev = nullptr;
×
149

150
    switch(id)
×
151
    {
152
        // Laufevent
153
        case 0:
×
154
        {
155
            // neue Position einnehmen
156
            Walk();
×
157

158
            // entscheiden, was als nächstes zu tun ist
159
            Walked();
×
160
        }
161
        break;
×
162
        // Warte-Event
163
        case 1:
×
164
        {
165
            // wieder weiterlaufen
166
            StandardWalking();
×
167
            // state entsprechen setzen, wenn es nich gestorben ist
168
            if(state != State::Dead)
×
169
                state = State::Walking;
×
170
        }
171
        break;
×
172
        // Sterbe-Event
173
        case 2:
×
174
        {
175
            // nun verschwinden
176
            current_ev = GetEvMgr().AddEvent(this, 30, 3);
×
177
            state = State::Disappearing;
×
178

179
            // Jäger ggf. Bescheid sagen (falls der es nicht mehr rechtzeitig schafft, bis ich verwest bin)
180
            if(hunter)
×
181
            {
182
                hunter->AnimalLost();
×
183
                hunter = nullptr;
×
184
            }
NEW
185
            if(skinner)
×
186
            {
NEW
187
                skinner->AnimalLost();
×
NEW
188
                skinner = nullptr;
×
189
            }
190
        }
191
        break;
×
192
        // Verschwind-Event
193
        case 3:
×
194
        {
195
            // von der Karte tilgen
196
            GetEvMgr().AddToKillList(world->RemoveFigure(pos, *this));
×
197
        }
198
        break;
×
199
    }
200
}
×
201

202
void noAnimal::StartWalking(const Direction dir)
401✔
203
{
204
    StartMoving(dir, ANIMALCONSTS[species].speed);
401✔
205
}
401✔
206

207
void noAnimal::StandardWalking()
430✔
208
{
209
    // neuen Weg suchen
210
    const helpers::OptionalEnum<Direction> dir = FindDir();
430✔
211
    if(!dir)
430✔
212
    {
213
        // Sterben, weil kein Weg mehr gefunden wurde
214
        Die();
29✔
215
        // Jäger ggf. Bescheid sagen (falls der es nicht mehr rechtzeitig schafft, bis ich verwest bin)
216
        if(hunter) //-V779
29✔
217
        {
218
            hunter->AnimalLost();
×
219
            hunter = nullptr;
×
220
        }
221
    } else
222
    {
223
        // weiterlaufen
224
        StartWalking(*dir);
401✔
225
    }
226
}
430✔
227

228
void noAnimal::Walked()
×
229
{
230
    switch(state)
×
231
    {
232
        default: break;
×
233
        case State::WalkingUntilWaitingForHunter:
×
234
        {
235
            // stehenbleiben und auf den Jäger warten
236
            state = State::WaitingForHunter;
×
237
        }
238
        break;
×
239
        case State::Walking:
×
240
        {
241
            // ein weiteres Stück gelaufen
242
            --pause_way;
×
243

244
            // Ist es so lange gelaufen, dass es mal wieder eine Pause braucht?
245
            if(!pause_way)
×
246
            {
247
                // dann stellt es sich hier hin und wartet erstmal eine Weile
248
                state = State::Paused;
×
249
                pause_way = 5 + RANDOM_RAND(15);
×
250
                current_ev = GetEvMgr().AddEvent(this, 50 + RANDOM_RAND(50), 1);
×
251
            } else
252
            {
253
                StandardWalking();
×
254
            }
255

256
            // Bei Enten und Schafen: Soundzeitpunkt ggf. setzen
257
            if(species == Species::Duck || species == Species::Sheep)
×
258
            {
259
                const unsigned now = VIDEODRIVER.GetTickCount();
×
260
                // Wurde der Soundzeitpunkt schon überschritten?
261
                if(now > sound_moment)
×
262
                    sound_moment = now + helpers::randomValue(getAnimalRng(), 8000, 13000);
×
263
            }
264
        }
265
        break;
×
266
    }
267
}
×
268

269
helpers::OptionalEnum<Direction> noAnimal::FindDir()
430✔
270
{
271
    // mit zufälliger Richtung anfangen
272
    const Direction doffset = RANDOM_ENUM(Direction);
430✔
273
    const WorldDescription& worldDesc = world->GetDescription();
430✔
274

275
    for(const auto dir : helpers::enumRange(doffset))
2,522✔
276
    {
277
        const auto terrains = world->GetTerrain(pos, dir);
802✔
278
        const DescIdx<TerrainDesc> tLeft = terrains.left;
802✔
279
        const DescIdx<TerrainDesc> tRight = terrains.right;
802✔
280

281
        if(species == Species::Duck)
802✔
282
        {
283
            // Enten schwimmen nur auf dem Wasser --> muss daher Wasser sein
284
            if(worldDesc.get(tLeft).kind == TerrainKind::Water && worldDesc.get(tRight).kind == TerrainKind::Water)
6✔
285
                return dir;
×
286
        } else if(species == Species::PolarBear)
796✔
287
        {
288
            // Polarbären laufen nur auf Schnee rum
289
            if(worldDesc.get(tLeft).kind == TerrainKind::Snow && worldDesc.get(tRight).kind == TerrainKind::Snow)
1✔
290
                return dir;
1✔
291
        } else
292
        {
293
            // Die anderen Tiere dürfen nur auf Wiesen,Savannen usw. laufen, nicht auf Bergen oder in der Wüste!
294
            if(!worldDesc.get(tLeft).IsUsableByAnimals() || !worldDesc.get(tRight).IsUsableByAnimals())
795✔
295
                continue;
395✔
296

297
            // Außerdem dürfen keine Hindernisse im Weg sein
298
            const MapPoint dst = world->GetNeighbour(pos, dir);
612✔
299
            const noBase* no = world->GetNO(dst);
612✔
300

301
            if(no->GetType() != NodalObjectType::Nothing && no->GetType() != NodalObjectType::Environment
1,216✔
302
               && no->GetType() != NodalObjectType::Tree)
1,216✔
303
                continue;
×
304

305
            // Schließlich auch möglichst keine anderen Figuren bzw. Tiere
306
            if(!world->GetFigures(dst).empty())
1,224✔
307
                continue;
212✔
308

309
            // Und möglichst auch keine Straßen
310
            for(const auto dir2 : helpers::EnumRange<Direction>{})
6,400✔
311
            {
312
                if(world->GetPointRoad(dst, dir2) != PointRoad::None)
2,400✔
313
                    return boost::none;
×
314
            }
315
            return dir;
400✔
316
        }
317
    }
318

319
    // kein Weg mehr gefunden
320
    return boost::none;
29✔
321
}
322

NEW
323
bool noAnimal::CanBeSkinned() const
×
324
{
NEW
325
    return (species != Species::Duck && state == State::Dead && !skinner);
×
326
}
327

NEW
328
bool noAnimal::IsGettingSkinned() const
×
329
{
NEW
330
    return skinner != nullptr;
×
331
}
332

NEW
333
void noAnimal::Skinned()
×
334
{
NEW
335
    if(!hunter)
×
336
        // Remove decay event for animal because skinner has taken it
NEW
337
        GetEvMgr().RemoveEvent(current_ev);
×
338
    // Reset skinner
NEW
339
    skinner = nullptr;
×
NEW
340
}
×
341

NEW
342
void noAnimal::BeginSkinning(nofSkinner* skinner)
×
343
{
NEW
344
    this->skinner = skinner;
×
NEW
345
}
×
346

NEW
347
void noAnimal::StopSkinning()
×
348
{
NEW
349
    skinner = nullptr;
×
NEW
350
}
×
351

352
bool noAnimal::CanHunted() const
9✔
353
{
354
    // Enten sowie Tiere, die bereits gejagt werden, oder schon tot daliegen, können nicht gejagt werden
355
    return (species != Species::Duck && state != State::Dead && state != State::Disappearing && !hunter);
9✔
356
}
357

NEW
358
bool noAnimal::IsHunted() const
×
359
{
NEW
360
    return hunter != nullptr;
×
361
}
362

363
void noAnimal::BeginHunting(nofHunter* hunter)
1✔
364
{
365
    this->hunter = hunter;
1✔
366
}
1✔
367

368
MapPoint noAnimal::HunterIsNear()
×
369
{
370
    // Steht es gerade?
371
    if(state == State::Paused)
×
372
    {
373
        // dann bleibt es einfach stehen und gibt seine jetzigen Koordinaten zurück
374
        state = State::WaitingForHunter;
×
375
        // Warteevent abmelden
376
        GetEvMgr().RemoveEvent(current_ev);
×
377
        return pos;
×
378
    } else
379
    {
380
        // ansonsten nach dem Laufen stehenbleiben und die Koordinaten zurückgeben von dem Punkt, der erreicht wird
381
        state = State::WalkingUntilWaitingForHunter;
×
382
        return world->GetNeighbour(pos, GetCurMoveDir());
×
383
    }
384
}
385

386
void noAnimal::StopHunting()
×
387
{
388
    // keiner jagt uns mehr
389
    hunter = nullptr;
×
390

391
    switch(state)
×
392
    {
393
        default: return;
×
394

395
        case State::WaitingForHunter:
×
396
        {
397
            // wenn wir stehen, zusätzlich loslaufen
398
            state = State::Walking;
×
399
            StandardWalking();
×
400
        }
401
        break;
×
402
        case State::WalkingUntilWaitingForHunter:
×
403
        {
404
            // wir können wieder normal weiterlaufen
405
            state = State::Walking;
×
406
        }
407
        break;
×
408
    }
409
}
410

411
void noAnimal::Die()
29✔
412
{
413
    // nun bin ich tot
414
    if(ANIMALCONSTS[species].dead_id)
29✔
415
    {
416
        // Verwesungsevent
417
        unsigned gf_length = leatheraddon::isAddonActive(*world) ? 600 : 300;
28✔
418
        current_ev = GetEvMgr().AddEvent(this, gf_length, 2);
28✔
419
        state = State::Dead;
28✔
420
    } else
421
    {
422
        // Falls keine Verwesungsgrafik --> sofort verschwinden
423
        current_ev = GetEvMgr().AddEvent(this, 30, 3);
1✔
424
        state = State::Disappearing;
1✔
425
    }
426
}
29✔
427

428
void noAnimal::Eviscerated()
×
429
{
430
    // Event abmelden
NEW
431
    if(!IsGettingSkinned())
×
NEW
432
        GetEvMgr().RemoveEvent(current_ev);
×
433
    // Reset hunter
434
    hunter = nullptr;
×
435
}
×
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