OpenTTD Source 20260218-master-g2123fca5ea
effectvehicle.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD 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, version 2.
4 * OpenTTD 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "stdafx.h"
11#include "landscape.h"
12#include "core/random_func.hpp"
13#include "industry_map.h"
14#include "vehicle_func.h"
15#include "sound_func.h"
16#include "animated_tile_func.h"
17#include "effectvehicle_func.h"
18#include "effectvehicle_base.h"
19
20#include "safeguards.h"
21
22
30{
31 if (v->sprite_cache.sprite_seq.seq[0].sprite != last) {
32 v->sprite_cache.sprite_seq.seq[0].sprite++;
33 return true;
34 } else {
35 return false;
36 }
37}
38
41{
42 uint32_t r = Random();
43 v->sprite_cache.sprite_seq.Set(SPR_CHIMNEY_SMOKE_0 + GB(r, 0, 3));
44 v->progress = GB(r, 16, 3);
45}
46
49{
50 if (v->progress > 0) {
51 v->progress--;
52 } else {
53 TileIndex tile = TileVirtXY(v->x_pos, v->y_pos);
54 if (!IsTileType(tile, TileType::Industry)) {
55 delete v;
56 return false;
57 }
58
59 if (!IncrementSprite(v, SPR_CHIMNEY_SMOKE_7)) {
60 v->sprite_cache.sprite_seq.Set(SPR_CHIMNEY_SMOKE_0);
61 }
62 v->progress = 7;
64 }
65
66 return true;
67}
68
71{
72 v->sprite_cache.sprite_seq.Set(SPR_STEAM_SMOKE_0);
73 v->progress = 12;
74}
75
78{
79 bool moved = false;
80
81 v->progress++;
82
83 if ((v->progress & 7) == 0) {
84 v->z_pos++;
85 moved = true;
86 }
87
88 if ((v->progress & 0xF) == 4) {
89 if (!IncrementSprite(v, SPR_STEAM_SMOKE_4)) {
90 delete v;
91 return false;
92 }
93 moved = true;
94 }
95
96 if (moved) v->UpdatePositionAndViewport();
97
98 return true;
99}
100
103{
104 v->sprite_cache.sprite_seq.Set(SPR_DIESEL_SMOKE_0);
105 v->progress = 0;
106}
107
110{
111 v->progress++;
112
113 if ((v->progress & 3) == 0) {
114 v->z_pos++;
116 } else if ((v->progress & 7) == 1) {
117 if (!IncrementSprite(v, SPR_DIESEL_SMOKE_5)) {
118 delete v;
119 return false;
120 }
122 }
123
124 return true;
125}
126
129{
130 v->sprite_cache.sprite_seq.Set(SPR_ELECTRIC_SPARK_0);
131 v->progress = 1;
132}
133
136{
137 if (v->progress < 2) {
138 v->progress++;
139 } else {
140 v->progress = 0;
141
142 if (!IncrementSprite(v, SPR_ELECTRIC_SPARK_5)) {
143 delete v;
144 return false;
145 }
147 }
148
149 return true;
150}
151
154{
155 v->sprite_cache.sprite_seq.Set(SPR_SMOKE_0);
156 v->progress = 12;
157}
158
161{
162 bool moved = false;
163
164 v->progress++;
165
166 if ((v->progress & 3) == 0) {
167 v->z_pos++;
168 moved = true;
169 }
170
171 if ((v->progress & 0xF) == 4) {
172 if (!IncrementSprite(v, SPR_SMOKE_4)) {
173 delete v;
174 return false;
175 }
176 moved = true;
177 }
178
179 if (moved) v->UpdatePositionAndViewport();
180
181 return true;
182}
183
186{
187 v->sprite_cache.sprite_seq.Set(SPR_EXPLOSION_LARGE_0);
188 v->progress = 0;
189}
190
193{
194 v->progress++;
195 if ((v->progress & 3) == 0) {
196 if (!IncrementSprite(v, SPR_EXPLOSION_LARGE_F)) {
197 delete v;
198 return false;
199 }
201 }
202
203 return true;
204}
205
208{
209 v->sprite_cache.sprite_seq.Set(SPR_BREAKDOWN_SMOKE_0);
210 v->progress = 0;
211}
212
215{
216 v->progress++;
217 if ((v->progress & 7) == 0) {
218 if (!IncrementSprite(v, SPR_BREAKDOWN_SMOKE_3)) {
219 v->sprite_cache.sprite_seq.Set(SPR_BREAKDOWN_SMOKE_0);
220 }
222 }
223
224 v->animation_state--;
225 if (v->animation_state == 0) {
226 delete v;
227 return false;
228 }
229
230 return true;
231}
232
235{
236 v->sprite_cache.sprite_seq.Set(SPR_EXPLOSION_SMALL_0);
237 v->progress = 0;
238}
239
242{
243 v->progress++;
244 if ((v->progress & 3) == 0) {
245 if (!IncrementSprite(v, SPR_EXPLOSION_SMALL_B)) {
246 delete v;
247 return false;
248 }
250 }
251
252 return true;
253}
254
257{
258 v->sprite_cache.sprite_seq.Set(SPR_BULLDOZER_NE);
259 v->progress = 0;
260 v->animation_state = 0;
261 v->animation_substate = 0;
262}
263
265 uint8_t direction:2;
266 uint8_t image:2;
267 uint8_t duration:3;
268};
269
270static const BulldozerMovement _bulldozer_movement[] = {
271 { 0, 0, 4 },
272 { 3, 3, 4 },
273 { 2, 2, 7 },
274 { 0, 2, 7 },
275 { 1, 1, 3 },
276 { 2, 2, 7 },
277 { 0, 2, 7 },
278 { 1, 1, 3 },
279 { 2, 2, 7 },
280 { 0, 2, 7 },
281 { 3, 3, 6 },
282 { 2, 2, 6 },
283 { 1, 1, 7 },
284 { 3, 1, 7 },
285 { 0, 0, 3 },
286 { 1, 1, 7 },
287 { 3, 1, 7 },
288 { 0, 0, 3 },
289 { 1, 1, 7 },
290 { 3, 1, 7 }
291};
292
293static const struct {
294 int8_t x;
295 int8_t y;
296} _inc_by_dir[] = {
297 { -1, 0 },
298 { 0, 1 },
299 { 1, 0 },
300 { 0, -1 }
301};
302
305{
306 v->progress++;
307 if ((v->progress & 7) == 0) {
308 const BulldozerMovement *b = &_bulldozer_movement[v->animation_state];
309
310 v->sprite_cache.sprite_seq.Set(SPR_BULLDOZER_NE + b->image);
311
312 v->x_pos += _inc_by_dir[b->direction].x;
313 v->y_pos += _inc_by_dir[b->direction].y;
314
316 if (v->animation_substate >= b->duration) {
317 v->animation_substate = 0;
318 v->animation_state++;
319 if (v->animation_state == lengthof(_bulldozer_movement)) {
320 delete v;
321 return false;
322 }
323 }
325 }
326
327 return true;
328}
329
332{
333 v->sprite_cache.sprite_seq.Set(SPR_BUBBLE_GENERATE_0);
334 v->spritenum = 0;
335 v->progress = 0;
336}
337
339 int8_t x:4;
340 int8_t y:4;
341 int8_t z:4;
342 uint8_t image:4;
343};
344
345#define MK(x, y, z, i) { x, y, z, i }
346#define ME(i) { i, 4, 0, 0 }
347
348static const BubbleMovement _bubble_float_sw[] = {
349 MK(0, 0, 1, 0),
350 MK(1, 0, 1, 1),
351 MK(0, 0, 1, 0),
352 MK(1, 0, 1, 2),
353 ME(1)
354};
355
356
357static const BubbleMovement _bubble_float_ne[] = {
358 MK( 0, 0, 1, 0),
359 MK(-1, 0, 1, 1),
360 MK( 0, 0, 1, 0),
361 MK(-1, 0, 1, 2),
362 ME(1)
363};
364
365static const BubbleMovement _bubble_float_se[] = {
366 MK(0, 0, 1, 0),
367 MK(0, 1, 1, 1),
368 MK(0, 0, 1, 0),
369 MK(0, 1, 1, 2),
370 ME(1)
371};
372
373static const BubbleMovement _bubble_float_nw[] = {
374 MK(0, 0, 1, 0),
375 MK(0, -1, 1, 1),
376 MK(0, 0, 1, 0),
377 MK(0, -1, 1, 2),
378 ME(1)
379};
380
381static const BubbleMovement _bubble_burst[] = {
382 MK(0, 0, 1, 2),
383 MK(0, 0, 1, 7),
384 MK(0, 0, 1, 8),
385 MK(0, 0, 1, 9),
386 ME(0)
387};
388
389static const BubbleMovement _bubble_absorb[] = {
390 MK(0, 0, 1, 0),
391 MK(0, 0, 1, 1),
392 MK(0, 0, 1, 0),
393 MK(0, 0, 1, 2),
394 MK(0, 0, 1, 0),
395 MK(0, 0, 1, 1),
396 MK(0, 0, 1, 0),
397 MK(0, 0, 1, 2),
398 MK(0, 0, 1, 0),
399 MK(0, 0, 1, 1),
400 MK(0, 0, 1, 0),
401 MK(0, 0, 1, 2),
402 MK(0, 0, 1, 0),
403 MK(0, 0, 1, 1),
404 MK(0, 0, 1, 0),
405 MK(0, 0, 1, 2),
406 MK(0, 0, 1, 0),
407 MK(0, 0, 1, 1),
408 MK(0, 0, 1, 0),
409 MK(0, 0, 1, 2),
410 MK(0, 0, 1, 0),
411 MK(0, 0, 1, 1),
412 MK(0, 0, 1, 0),
413 MK(0, 0, 1, 2),
414 MK(0, 0, 1, 0),
415 MK(0, 0, 1, 1),
416 MK(0, 0, 1, 0),
417 MK(0, 0, 1, 2),
418 MK(0, 0, 1, 0),
419 MK(0, 0, 1, 1),
420 MK(0, 0, 1, 0),
421 MK(0, 0, 1, 2),
422 MK(0, 0, 1, 0),
423 MK(0, 0, 1, 1),
424 MK(0, 0, 1, 0),
425 MK(0, 0, 1, 2),
426 MK(0, 0, 1, 0),
427 MK(0, 0, 1, 1),
428 MK(0, 0, 1, 0),
429 MK(0, 0, 1, 2),
430 MK(0, 0, 1, 0),
431 MK(0, 0, 1, 1),
432 MK(0, 0, 1, 0),
433 MK(0, 0, 1, 2),
434 MK(0, 0, 1, 0),
435 MK(0, 0, 1, 1),
436 MK(0, 0, 1, 0),
437 MK(0, 0, 1, 2),
438 MK(0, 0, 1, 0),
439 MK(0, 0, 1, 1),
440 MK(0, 0, 1, 0),
441 MK(0, 0, 1, 2),
442 MK(0, 0, 1, 0),
443 MK(0, 0, 1, 1),
444 MK(0, 0, 1, 0),
445 MK(0, 0, 1, 2),
446 MK(0, 0, 1, 0),
447 MK(0, 0, 1, 1),
448 MK(0, 0, 1, 0),
449 MK(0, 0, 1, 2),
450 MK(0, 0, 1, 0),
451 MK(0, 0, 1, 1),
452 MK(2, 1, 3, 0),
453 MK(1, 1, 3, 1),
454 MK(2, 1, 3, 0),
455 MK(1, 1, 3, 2),
456 MK(2, 1, 3, 0),
457 MK(1, 1, 3, 1),
458 MK(2, 1, 3, 0),
459 MK(1, 0, 1, 2),
460 MK(0, 0, 1, 0),
461 MK(1, 0, 1, 1),
462 MK(0, 0, 1, 0),
463 MK(1, 0, 1, 2),
464 MK(0, 0, 1, 0),
465 MK(1, 0, 1, 1),
466 MK(0, 0, 1, 0),
467 MK(1, 0, 1, 2),
468 ME(2),
469 MK(0, 0, 0, 0xA),
470 MK(0, 0, 0, 0xB),
471 MK(0, 0, 0, 0xC),
472 MK(0, 0, 0, 0xD),
473 MK(0, 0, 0, 0xE),
474 ME(0)
475};
476#undef ME
477#undef MK
478
479static const BubbleMovement * const _bubble_movement[] = {
480 _bubble_float_sw,
481 _bubble_float_ne,
482 _bubble_float_se,
483 _bubble_float_nw,
484 _bubble_burst,
485 _bubble_absorb,
486};
487
490{
491 uint anim_state;
492
493 v->progress++;
494 if ((v->progress & 3) != 0) return true;
495
496 if (v->spritenum == 0) {
497 v->sprite_cache.sprite_seq.seq[0].sprite++;
498 if (v->sprite_cache.sprite_seq.seq[0].sprite < SPR_BUBBLE_GENERATE_3) {
500 return true;
501 }
502 if (v->animation_substate != 0) {
503 v->spritenum = GB(Random(), 0, 2) + 1;
504 } else {
505 v->spritenum = 6;
506 }
507 anim_state = 0;
508 } else {
509 anim_state = v->animation_state + 1;
510 }
511
512 const BubbleMovement *b = &_bubble_movement[v->spritenum - 1][anim_state];
513
514 if (b->y == 4 && b->x == 0) {
515 delete v;
516 return false;
517 }
518
519 if (b->y == 4 && b->x == 1) {
520 if (v->z_pos > 180 || Chance16I(1, 96, Random())) {
521 v->spritenum = 5;
522 if (_settings_client.sound.ambient) SndPlayVehicleFx(SND_2F_BUBBLE_GENERATOR_FAIL, v);
523 }
524 anim_state = 0;
525 }
526
527 if (b->y == 4 && b->x == 2) {
528 TileIndex tile;
529
530 anim_state++;
531 if (_settings_client.sound.ambient) SndPlayVehicleFx(SND_31_BUBBLE_GENERATOR_SUCCESS, v);
532
533 tile = TileVirtXY(v->x_pos, v->y_pos);
534 if (IsTileType(tile, TileType::Industry) && GetIndustryGfx(tile) == GFX_BUBBLE_CATCHER) AddAnimatedTile(tile);
535 }
536
537 v->animation_state = anim_state;
538 b = &_bubble_movement[v->spritenum - 1][anim_state];
539
540 v->x_pos += b->x;
541 v->y_pos += b->y;
542 v->z_pos += b->z;
543 v->sprite_cache.sprite_seq.Set(SPR_BUBBLE_0 + b->image);
544
546
547 return true;
548}
549
572
574static const std::array<EffectProcs, EV_END> _effect_procs = {{
575 { ChimneySmokeInit, ChimneySmokeTick, TO_INDUSTRIES }, // EV_CHIMNEY_SMOKE
576 { SteamSmokeInit, SteamSmokeTick, TO_INVALID }, // EV_STEAM_SMOKE
577 { DieselSmokeInit, DieselSmokeTick, TO_INVALID }, // EV_DIESEL_SMOKE
578 { ElectricSparkInit, ElectricSparkTick, TO_INVALID }, // EV_ELECTRIC_SPARK
579 { SmokeInit, SmokeTick, TO_INVALID }, // EV_CRASH_SMOKE
580 { ExplosionLargeInit, ExplosionLargeTick, TO_INVALID }, // EV_EXPLOSION_LARGE
581 { BreakdownSmokeInit, BreakdownSmokeTick, TO_INVALID }, // EV_BREAKDOWN_SMOKE
582 { ExplosionSmallInit, ExplosionSmallTick, TO_INVALID }, // EV_EXPLOSION_SMALL
583 { BulldozerInit, BulldozerTick, TO_INVALID }, // EV_BULLDOZER
584 { BubbleInit, BubbleTick, TO_INDUSTRIES }, // EV_BUBBLE
585 { SmokeInit, SmokeTick, TO_INVALID }, // EV_BREAKDOWN_SMOKE_AIRCRAFT
586 { SmokeInit, SmokeTick, TO_INDUSTRIES }, // EV_COPPER_MINE_SMOKE
587}};
588
598{
599 if (!Vehicle::CanAllocateItem()) return nullptr;
600
602 v->subtype = type;
603 v->x_pos = x;
604 v->y_pos = y;
605 v->z_pos = z;
606 v->tile = TileIndex{};
607 v->UpdateDeltaXY();
609
610 _effect_procs[type].init_proc(v);
611
613
614 return v;
615}
616
626{
627 int safe_x = Clamp(x, 0, Map::MaxX() * TILE_SIZE);
628 int safe_y = Clamp(y, 0, Map::MaxY() * TILE_SIZE);
629 return CreateEffectVehicle(x, y, GetSlopePixelZ(safe_x, safe_y) + z, type);
630}
631
642{
643 return CreateEffectVehicle(v->x_pos + x, v->y_pos + y, v->z_pos + z, type);
644}
645
647{
648 return _effect_procs[this->subtype].tick_proc(this);
649}
650
652{
653 this->bounds = {{}, {1, 1, 1}, {}};
654}
655
661{
662 return _effect_procs[this->subtype].transparency;
663}
void AddAnimatedTile(TileIndex tile, bool mark_dirty)
Add the given tile to the animated tile table (if it does not exist yet).
Tile animation!
static constexpr uint GB(const T x, const uint8_t s, const uint8_t n)
Fetch n bits from x, started at bit s.
static void ElectricSparkInit(EffectVehicle *v)
Initialise the sparks of a train.
static void ExplosionLargeInit(EffectVehicle *v)
Initialise a large explosion.
static void BubbleInit(EffectVehicle *v)
Initialise the bubbles of the bubble generator industry.
static void DieselSmokeInit(EffectVehicle *v)
Initialise the smoke of a diesel engine.
static void ChimneySmokeInit(EffectVehicle *v)
Initialise the smoke of a chimney.
static bool BulldozerTick(EffectVehicle *v)
Run a single tick of a bulldozer (road works).
EffectVehicle * CreateEffectVehicleRel(const Vehicle *v, int x, int y, int z, EffectVehicleType type)
Create an effect vehicle above a particular vehicle.
static bool DieselSmokeTick(EffectVehicle *v)
Run a single tick of the smoke of a diesel engine.
EffectVehicle * CreateEffectVehicle(int x, int y, int z, EffectVehicleType type)
Create an effect vehicle at a particular location.
static bool IncrementSprite(EffectVehicle *v, SpriteID last)
Increment the sprite unless it has reached the end of the animation.
static bool BreakdownSmokeTick(EffectVehicle *v)
Run a single tick of the smoke of a broken down vehicle.
static const std::array< EffectProcs, EV_END > _effect_procs
Per-EffectVehicleType handling.
EffectVehicle * CreateEffectVehicleAbove(int x, int y, int z, EffectVehicleType type)
Create an effect vehicle above a particular location.
static void SteamSmokeInit(EffectVehicle *v)
Initialise the smoke of a steam engine.
static bool ElectricSparkTick(EffectVehicle *v)
Run a single tick of the sparks of a train.
static void BreakdownSmokeInit(EffectVehicle *v)
Initialise the smoke of a broken down vehicle.
static bool SteamSmokeTick(EffectVehicle *v)
Run a single tick of the smoke of a steam engine.
static void ExplosionSmallInit(EffectVehicle *v)
Initialise a small explosion.
static void BulldozerInit(EffectVehicle *v)
Initialise the bulldozer (road works).
static bool ChimneySmokeTick(EffectVehicle *v)
Run a single tick of the smoke of a chimney.
static bool SmokeTick(EffectVehicle *v)
Run a single tick of some smoke.
static bool BubbleTick(EffectVehicle *v)
Run a single tick of bubbles of the bubble generator industry.
static void SmokeInit(EffectVehicle *v)
Initialise some smoke.
static bool ExplosionSmallTick(EffectVehicle *v)
Run a single tick of a small explosion.
static bool ExplosionLargeTick(EffectVehicle *v)
Run a single tick of a large explosion.
Base class for all effect vehicles.
Functions related to effect vehicles.
EffectVehicleType
Effect vehicle types.
uint32_t SpriteID
The number of a sprite, without mapping bits and colourtables.
Definition gfx_type.h:17
Accessors to map for industries.
IndustryGfx GetIndustryGfx(Tile t)
Get the industry graphics ID for the given industry tile.
int GetSlopePixelZ(int x, int y, bool ground_vehicle)
Return world Z coordinate of a given point of a tile.
Functions related to OTTD's landscape.
static TileIndex TileVirtXY(uint x, uint y)
Get a tile from the virtual XY-coordinate.
Definition map_func.h:409
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
Pseudo random number generator.
bool Chance16I(const uint32_t a, const uint32_t b, const uint32_t r)
Checks if a given randomize-number is below a given probability.
A number of safeguards to prevent using unsafe methods.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
#define MK(a, b)
Macro for ordinary entry of LegendAndColour.
Functions related to sound.
@ SND_31_BUBBLE_GENERATOR_SUCCESS
49 == 0x31 Industry animation: bubble generator (2b): bubble slurped
Definition sound_type.h:97
@ SND_2F_BUBBLE_GENERATOR_FAIL
47 == 0x2F Industry animation: bubble generator (2a): bubble pop
Definition sound_type.h:95
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:271
bool(EffectVehicle *v) TickProc
Run the actions/perform the behaviour of an effect vehicle for a tick.
void(EffectVehicle *v) InitProc
Initialises effect vehicle for a specific type.
InitProc * init_proc
Function to initialise an effect vehicle after construction.
TickProc * tick_proc
Functions for controlling effect vehicles at each tick.
TransparencyOption transparency
Transparency option affecting the effect.
A special vehicle is one of the following:
void UpdateDeltaXY() override
Updates the x and y offsets and the size of the sprite used for this vehicle.
TransparencyOption GetTransparencyOption() const
Determines the transparency option affecting the effect.
uint8_t animation_substate
Sub state to time the change of the graphics/behaviour.
bool Tick() override
Calls the tick handler of the vehicle.
uint16_t animation_state
State primarily used to change the graphics/behaviour.
static uint MaxY()
Gets the maximum Y coordinate within the map, including TileType::Void.
Definition map_func.h:300
static uint MaxX()
Gets the maximum X coordinate within the map, including TileType::Void.
Definition map_func.h:291
VehicleSpriteSeq sprite_seq
Vehicle appearance.
static bool CanAllocateItem(size_t n=1)
static T * Create(Targs &&... args)
void Set(SpriteID sprite)
Assign a single sprite to the sequence.
Vehicle data structure.
int32_t z_pos
z coordinate.
uint8_t subtype
subtype (Filled with values from AircraftSubType/DisasterSubType/EffectVehicleType/GroundVehicleSubty...
VehStates vehstatus
Status.
int32_t y_pos
y coordinate.
int32_t x_pos
x coordinate.
SpriteBounds bounds
Bounding box of vehicle.
uint8_t spritenum
currently displayed sprite index 0xfd == custom sprite, 0xfe == custom second head sprite 0xff == res...
MutableSpriteCache sprite_cache
Cache of sprites and values related to recalculating them, see MutableSpriteCache.
uint8_t progress
The percentage (if divided by 256) this vehicle already crossed the tile unit.
void UpdatePositionAndViewport()
Update the position of the vehicle, and update the viewport.
Definition vehicle.cpp:1757
TileIndex tile
Current tile index.
static bool IsTileType(Tile tile, TileType type)
Checks if a tile is a given tiletype.
Definition tile_map.h:150
StrongType::Typedef< uint32_t, struct TileIndexTag, StrongType::Compare, StrongType::Integer, StrongType::Compatible< int32_t >, StrongType::Compatible< int64_t > > TileIndex
The index/ID of a Tile.
Definition tile_type.h:92
static constexpr uint TILE_SIZE
Tile size in world coordinates.
Definition tile_type.h:15
@ Industry
Part of an industry.
Definition tile_type.h:57
TransparencyOption
Transparency option bits: which position in _transparency_opt stands for which transparency.
@ TO_INVALID
Invalid transparency option.
@ TO_INDUSTRIES
industries
@ Unclickable
Vehicle is not clickable by the user (shadow vehicles).
Functions related to vehicles.