From 34d51d9e6ca66dc46b3252bae5514bbd3ec2f38d Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 12 May 2017 22:03:27 +0200 Subject: [PATCH 1/8] BUG 2322: Test and bugfix for castle names. --- scripts/tests/common.lua | 45 ++++++++++++++++++++++++++++++++++++++++ src/kernel/building.c | 3 --- src/kernel/building.h | 37 +++++++++++++++++---------------- 3 files changed, 64 insertions(+), 21 deletions(-) diff --git a/scripts/tests/common.lua b/scripts/tests/common.lua index b8156a777..a9dcf2468 100644 --- a/scripts/tests/common.lua +++ b/scripts/tests/common.lua @@ -1032,3 +1032,48 @@ function test_recruit() assert_equal(6, u.number) end end + +function test_give_horses() + local r = region.create(0, 0, "plain") + local f = create_faction('human') + local u = unit.create(f, r, 1) + + r:set_resource("horse", 0) + u:add_item("horse", 21) + u:add_item("dolphin", 10) + u:add_order("GIB 0 7 PFERD") + u:add_order("GIB 0 5 DELPHIN") + process_orders() + assert_equal(7, r:get_resource("horse")) + assert_equal(5, u:get_item("dolphin")) + assert_equal(14, u:get_item("horse")) +end + +function test_give_silver() + local r = region.create(0, 0, "plain") + local f = create_faction('human') + local u = unit.create(f, r, 1) + + r:set_resource("peasant", 0) + r:set_resource("money", 11) + u:clear_orders() + u:add_item("money", 20) + u:add_order("GIB 0 10 SILBER") + process_orders() + assert_equal(21, r:get_resource("money")) + assert_equal(10, u:get_item("money")) +end + +function test_build_castle() + local r = region.create(0, 0, "plain") + local f = create_faction('human') + local u = unit.create(f, r, 1) + + u:add_item('stone', 1) + u:set_skill('building', 1) + u:add_order("MACHE BURG") + process_orders() + assert_not_nil(u.building) + assert_equal(1, u.building.size) + assert_equal(u.building.name, "Burg") +end diff --git a/src/kernel/building.c b/src/kernel/building.c index 6d0db8f43..6152622e8 100644 --- a/src/kernel/building.c +++ b/src/kernel/building.c @@ -406,9 +406,6 @@ building *new_building(const struct building_type * btype, region * r, *bptr = b; update_lighthouse(b); - if (b->type->name) { - bname = LOC(lang, buildingtype(btype, b, 0)); - } if (!bname) { bname = LOC(lang, btype->_name); } diff --git a/src/kernel/building.h b/src/kernel/building.h index 6d66aa9ab..9cb675fc1 100644 --- a/src/kernel/building.h +++ b/src/kernel/building.h @@ -77,6 +77,7 @@ extern "C" { extern struct selist *buildingtypes; extern struct attrib_type at_building_action; + extern struct attrib_type at_building_generic_type; int cmp_castle_size(const struct building *b, const struct building *a); int building_protection(const struct building_type *btype, int stage); @@ -117,13 +118,13 @@ extern "C" { int flags; } building; - extern struct attrib_type at_building_generic_type; - extern const char *buildingtype(const building_type * btype, + + const char *buildingtype(const building_type * btype, const struct building *b, int bsize); - extern const char *write_buildingname(const building * b, char *ibuf, + const char *write_buildingname(const building * b, char *ibuf, size_t size); - extern int buildingcapacity(const struct building *b); - extern struct building *new_building(const struct building_type *typ, + int buildingcapacity(const struct building *b); + struct building *new_building(const struct building_type *typ, struct region *r, const struct locale *lang); int build_building(struct unit *u, const struct building_type *typ, int id, int size, struct order *ord); @@ -147,9 +148,9 @@ extern "C" { void bunhash(struct building *b); int buildingcapacity(const struct building *b); - extern void remove_building(struct building **blist, struct building *b); - extern void free_building(struct building *b); - extern void free_buildings(void); + void remove_building(struct building **blist, struct building *b); + void free_building(struct building *b); + void free_buildings(void); const struct building_type *findbuildingtype(const char *name, const struct locale *lang); @@ -157,16 +158,16 @@ extern "C" { #include "build.h" #define NOBUILDING NULL - extern int resolve_building(variant data, void *address); - extern void write_building_reference(const struct building *b, + int resolve_building(variant data, void *address); + void write_building_reference(const struct building *b, struct storage *store); - extern variant read_building_reference(struct gamedata *data); + variant read_building_reference(struct gamedata *data); - extern struct building *findbuilding(int n); + struct building *findbuilding(int n); - extern struct unit *building_owner(const struct building *b); - extern void building_set_owner(struct unit * u); - extern void building_update_owner(struct building * bld); + struct unit *building_owner(const struct building *b); + void building_set_owner(struct unit * u); + void building_update_owner(struct building * bld); bool buildingtype_exists(const struct region *r, const struct building_type *bt, bool working); @@ -174,10 +175,10 @@ extern "C" { bool is_building_type(const struct building_type *btype, const char *name); struct building *active_building(const struct unit *u, const struct building_type *btype); - extern const char *buildingname(const struct building *b); + const char *buildingname(const struct building *b); - extern const char *building_getname(const struct building *b); - extern void building_setname(struct building *self, const char *name); + const char *building_getname(const struct building *b); + void building_setname(struct building *self, const char *name); struct region *building_getregion(const struct building *b); void building_setregion(struct building *bld, struct region *r); From 2c9c4c23f0dcb628ba57c11ff463b1cfc42fa7a0 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 11 Jun 2017 14:47:33 +0200 Subject: [PATCH 2/8] convert warning to debug output --- src/magic.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/magic.c b/src/magic.c index 81a797e1c..883bddffa 100644 --- a/src/magic.c +++ b/src/magic.c @@ -3014,7 +3014,7 @@ int cast_spell(struct castorder *co) fun = get_spellcast(sp->sname); if (!fun) { - log_warning("no spell function for %s, try callback", sp->sname); + log_debug("no spell function for %s, try callback", sp->sname); return callbacks.cast_spell(co, fname); } return fun(co); From bcf103f5817dc68a93c830457b24e03c2d31d8aa Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 11 Jun 2017 15:17:04 +0200 Subject: [PATCH 3/8] fix raindance_effect message in E2 --- conf/e3/locales.xml | 1 - res/core/messages.xml | 7 +++++++ res/e3a/messages.xml | 10 ---------- 3 files changed, 7 insertions(+), 11 deletions(-) delete mode 100644 res/e3a/messages.xml diff --git a/conf/e3/locales.xml b/conf/e3/locales.xml index 2d94bf202..bb7245fa3 100644 --- a/conf/e3/locales.xml +++ b/conf/e3/locales.xml @@ -4,7 +4,6 @@ - diff --git a/res/core/messages.xml b/res/core/messages.xml index 5d8a60ef0..5bb36e345 100644 --- a/res/core/messages.xml +++ b/res/core/messages.xml @@ -2033,6 +2033,13 @@ $if($isnull($mage),"Ein unentdeckter Magier",$unit($mage)) erschuf einen heiligen Hain von $int($amount) Schößlingen. $if($isnull($mage),"An unknown magician",$unit($mage)) created a holy forest of $int($amount) young trees. + + + + + "$if($isnull($mage),"Ein unentdeckter Magier",$unit($mage)) führt einen sonderbaren Tanz auf. Kurz darauf beginnt es zu regnen." + "$if($isnull($mage),"an unseen magician",$unit($mage)) dances a strange dance. Shortly after, rain begins to fall on the fields." + diff --git a/res/e3a/messages.xml b/res/e3a/messages.xml deleted file mode 100644 index 03e5b6eb4..000000000 --- a/res/e3a/messages.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - "$if($isnull($mage),"Ein unentdeckter Magier",$unit($mage)) führt einen sonderbaren Tanz auf. Kurz darauf beginnt es zu regnen." - "$if($isnull($mage),"an unseen magician",$unit($mage)) dances a strange dance. Shortly after, rain begins to fall on the fields." - - From f6ba5b6a53b6e56a1c786669ac5d0496baa97f75 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 11 Jun 2017 17:06:13 +0200 Subject: [PATCH 4/8] BUG 2333 NMR-Anzahl im WB falsch https://bugs.eressea.de/view.php?id=2333 --- scripts/run-turn.lua | 6 ++++-- src/kernel/config.c | 1 + src/kernel/save.c | 5 +---- src/summary.c | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/run-turn.lua b/scripts/run-turn.lua index 9118ed8ee..3ec87c045 100644 --- a/scripts/run-turn.lua +++ b/scripts/run-turn.lua @@ -142,8 +142,9 @@ function process(rules, orders) return -1 end - callbacks(rules, 'init') + turn_begin() init_summary() + callbacks(rules, 'init') -- run the turn: if eressea.read_orders(orders) ~= 0 then @@ -157,12 +158,13 @@ function process(rules, orders) return -1 end - process_orders() callbacks(rules, 'update') + turn_process() write_files(config.locales) dbupdate() + turn_end() file = '' .. get_turn() .. '.dat' if eressea.write_game(file)~=0 then eressea.log.error("could not write game") diff --git a/src/kernel/config.c b/src/kernel/config.c index 37765914b..566b9fc88 100644 --- a/src/kernel/config.c +++ b/src/kernel/config.c @@ -869,6 +869,7 @@ const char * game_mailcmd(void) } *r = '\0'; log_warning("game.mailcmd configuration is not set, using %s from game.name", result); + config_set("game.mailcmd", result); return result; } return param; diff --git a/src/kernel/save.c b/src/kernel/save.c index 39f2a129f..b28482e97 100644 --- a/src/kernel/save.c +++ b/src/kernel/save.c @@ -241,10 +241,7 @@ static faction *factionorders(void) } /* Die Partei hat sich zumindest gemeldet, so dass sie noch * nicht als untätig gilt */ - - /* TODO: +1 ist ein Workaround, weil cturn erst in process_orders - * incrementiert wird. */ - f->lastorders = turn + 1; + f->lastorders = turn; } else { diff --git a/src/summary.c b/src/summary.c index 90cab98c0..916124815 100644 --- a/src/summary.c +++ b/src/summary.c @@ -92,7 +92,7 @@ int update_nmrs(void) ++newplayers; } else if (!fval(f, FFL_NOIDLEOUT|FFL_CURSED)) { - int nmr = turn - f->lastorders + 1; + int nmr = turn - f->lastorders; if (timeout>0) { if (nmr < 0 || nmr > timeout) { log_error("faction %s has %d NMR", itoa36(f->no), nmr); From 3ec1476da2b95a1fb1dec6cedfbbd0f0e51d4ee7 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 11 Jun 2017 17:13:31 +0200 Subject: [PATCH 5/8] improve the mailcmd test --- src/kernel/config.test.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/kernel/config.test.c b/src/kernel/config.test.c index 40adaa364..ac35b85a3 100644 --- a/src/kernel/config.test.c +++ b/src/kernel/config.test.c @@ -278,6 +278,7 @@ static void test_game_mailcmd(CuTest *tc) { CuAssertStrEquals(tc, "Eressea", game_name()); CuAssertStrEquals(tc, "ERESSEA", game_mailcmd()); config_set("game.name", "Hodor"); + config_set("game.mailcmd", NULL); CuAssertStrEquals(tc, "Hodor", game_name()); CuAssertStrEquals(tc, "HODOR", game_mailcmd()); config_set("game.mailcmd", "ERESSEA"); From eed93b747070033e80b291074cc60da93f7ce2b7 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 18 Jun 2017 12:23:19 +0200 Subject: [PATCH 6/8] BUG 2334 and 2339: fix script update and init calls Ponnuki and Portals scripts are working. Ponnuki may still attack? More testing required. --- scripts/eressea/ponnuki.lua | 25 ++++++++++++++----------- scripts/run-turn.lua | 10 ++++------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/scripts/eressea/ponnuki.lua b/scripts/eressea/ponnuki.lua index 1a5d1959e..b5ba58fbe 100644 --- a/scripts/eressea/ponnuki.lua +++ b/scripts/eressea/ponnuki.lua @@ -10,23 +10,25 @@ local jokes = { } local function ponnuki_brain(u) - local i = math.random(#jokes) - u:add_notice("Eine Botschaft von " .. tostring(u) .. ": " ..jokes[i]) - local d = math.random(6) - local r = u.region:next(d-1) - if r.terrain == 'glacier' then u:clear_orders() - u:add_order("NACH " .. directions[d]) - end + local i = math.random(#jokes) + u:add_order("BOTSCHAFT REGION \"" .. jokes[i] .. "\"") + eressea.log.info("Ponnuki is telling jokes in " .. tostring(u.region)) + local d = math.random(6) + local r = u.region:next(d-1) + if r.terrain == 'glacier' then + u:add_order("NACH " .. directions[d]) + eressea.log.info("Ponnuki is walking to " .. tostring(r)) + end end function ponnuki.init() -- initialize other scripts + local f = get_faction(666) local u = get_unit(atoi36("ponn")) if not u then eressea.log.error("Ponnuki is missing, will re-create") local home = get_region(-67, -5) - local f = get_faction(666) if home and f then if home.terrain~="glacier" then home.terrain="glacier" @@ -44,10 +46,11 @@ function ponnuki.init() else eressea.log.error("Ponnuki cannot find Magrathea") end + elseif u.faction==f then + eressea.log.info("Ponnuki is in " .. tostring(u.region)) + u.status = 5 -- FLIEHE end - if u then - ponnuki_brain(u) - end + ponnuki_brain(u) end return ponnuki diff --git a/scripts/run-turn.lua b/scripts/run-turn.lua index 3ec87c045..4099c7976 100644 --- a/scripts/run-turn.lua +++ b/scripts/run-turn.lua @@ -144,7 +144,6 @@ function process(rules, orders) turn_begin() init_summary() - callbacks(rules, 'init') -- run the turn: if eressea.read_orders(orders) ~= 0 then @@ -152,19 +151,18 @@ function process(rules, orders) return -1 end - plan_monsters() - if nmr_check(config.maxnmrs or 80)~=0 then return -1 end - - callbacks(rules, 'update') + plan_monsters() + callbacks(rules, 'init') turn_process() + callbacks(rules, 'update') + turn_end() -- ageing, etc. write_files(config.locales) dbupdate() - turn_end() file = '' .. get_turn() .. '.dat' if eressea.write_game(file)~=0 then eressea.log.error("could not write game") From 8178f3f1e6ef931dd451b4ab1cbc0cf81f8392f7 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 18 Jun 2017 14:20:01 +0200 Subject: [PATCH 7/8] BUG 2334: Nochmal Ponnuki Monsters cannot attack or guard when they are fleeing --- src/kernel/save.c | 8 ++------ src/monsters.c | 35 +++++++++++++++++++---------------- src/monsters.test.c | 6 +++--- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/kernel/save.c b/src/kernel/save.c index b28482e97..e53d6ea4d 100644 --- a/src/kernel/save.c +++ b/src/kernel/save.c @@ -760,7 +760,7 @@ unit *read_unit(struct gamedata *data) assert(u_race(u)); for (;;) { - int n, level, weeks; + int n = NOSKILL, level, weeks; skill_t sk; READ_INT(data->store, &n); sk = (skill_t)n; @@ -1662,7 +1662,7 @@ int read_game(gamedata *data) { if (rmax < 0) { rmax = nread; } - log_debug(" - Einzulesende Regionen: %d/%d\r", rmax, nread); + log_debug(" - Einzulesende Regionen: %d/%d", rmax, nread); while (--nread >= 0) { unit **up; @@ -1716,10 +1716,6 @@ int read_game(gamedata *data) { update_interval(u->faction, r); } - - if ((nread & 0x3FF) == 0) { /* das spart extrem Zeit */ - log_debug(" - Einzulesende Regionen: %d/%d * %d,%d \r", rmax, nread, r->x, r->y); - } --rmax; } read_borders(data); diff --git a/src/monsters.c b/src/monsters.c index 62c982532..ffc04b52e 100644 --- a/src/monsters.c +++ b/src/monsters.c @@ -187,25 +187,28 @@ void monsters_desert(struct faction *monsters) } } -int monster_attacks(unit * monster, bool respect_buildings, bool rich_only) +int monster_attacks(unit * monster, bool rich_only) { - region *r = monster->region; - unit *u2; - int money = 0; + if (monster->status < ST_AVOID) { + region *r = monster->region; + unit *u2; + int money = 0; - for (u2 = r->units; u2; u2 = u2->next) { - if (u2->faction != monster->faction && cansee(monster->faction, r, u2, 0) && !in_safe_building(u2, monster)) { - int m = get_money(u2); - if (!rich_only || m > 0) { - order *ord = monster_attack(monster, u2); - if (ord) { - addlist(&monster->orders, ord); - money += m; + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction != monster->faction && cansee(monster->faction, r, u2, 0) && !in_safe_building(u2, monster)) { + int m = get_money(u2); + if (!rich_only || m > 0) { + order *ord = monster_attack(monster, u2); + if (ord) { + addlist(&monster->orders, ord); + money += m; + } } } } + return money; } - return money; + return 0; } static order *get_money_for_dragon(region * r, unit * udragon, int wanted) @@ -226,7 +229,7 @@ static order *get_money_for_dragon(region * r, unit * udragon, int wanted) * und holt sich Silber von Einheiten, vorausgesetzt er bewacht bereits */ money = 0; if (attacks && is_guard(udragon)) { - money += monster_attacks(udragon, true, true); + money += monster_attacks(udragon, true); } /* falls die einnahmen erreicht werden, bleibt das monster noch eine */ @@ -749,7 +752,7 @@ void plan_monsters(faction * f) } if (attacking && (!r->land || is_guard(u))) { - monster_attacks(u, true, false); + monster_attacks(u, false); } /* units with a plan to kill get ATTACK orders: */ @@ -773,7 +776,7 @@ void plan_monsters(faction * f) } /* All monsters guard the region: */ - if (!monster_is_waiting(u) && r->land) { + if (u->status < ST_FLEE && !monster_is_waiting(u) && r->land) { addlist(&u->orders, create_order(K_GUARD, u->faction->locale, NULL)); } diff --git a/src/monsters.test.c b/src/monsters.test.c index 3f2782173..b09bb23de 100644 --- a/src/monsters.test.c +++ b/src/monsters.test.c @@ -27,7 +27,7 @@ #include extern void plan_monsters(struct faction *f); -extern int monster_attacks(unit * monster, bool respect_buildings, bool rich_only); +extern int monster_attacks(unit * monster, bool rich_only); static order *find_order(const char *expected, const unit *unit) { @@ -64,6 +64,7 @@ static void create_monsters(faction **player, faction **monsters, unit **u, unit *u = test_create_unit(*player, r); unit_setid(*u, 1); *m = test_create_unit(*monsters, r); + unit_setstatus(*m, ST_FIGHT); } static void test_monsters_attack(CuTest * tc) @@ -72,7 +73,6 @@ static void test_monsters_attack(CuTest * tc) unit *u, *m; create_monsters(&f, &f2, &u, &m); - setguard(m, true); config_set("rules.monsters.attack_chance", "1"); @@ -112,7 +112,7 @@ static void test_monsters_waiting(CuTest * tc) create_monsters(&f, &f2, &u, &m); setguard(m, true); fset(m, UFL_ISNEW); - monster_attacks(m, false, false); + monster_attacks(m, false); CuAssertPtrEquals(tc, 0, find_order("attack 1", m)); test_cleanup(); } From 75ae22e5b6d83f6268509c77f34358cacce194c8 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 18 Jun 2017 21:48:47 +0200 Subject: [PATCH 8/8] BUG 2340: endloser Sturmelementar CURSE_NOAGE is a weird thing. --- src/kernel/curse.c | 26 ++++++++++++-------------- src/spells.c | 1 - src/spells/buildingcurse.c | 2 +- src/spells/flyingship.c | 6 ++---- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/kernel/curse.c b/src/kernel/curse.c index 25f89ec1f..01e77eace 100644 --- a/src/kernel/curse.c +++ b/src/kernel/curse.c @@ -107,24 +107,21 @@ void curse_init(attrib * a) int curse_age(attrib * a, void *owner) { curse *c = (curse *)a->data.v; - int result = 0; - + int result = AT_AGE_KEEP; UNUSED_ARG(owner); c_clearflag(c, CURSE_ISNEW); - if (c_flags(c) & CURSE_NOAGE) { - c->duration = INT_MAX; + if ((c_flags(c) & CURSE_NOAGE) == 0) { + if (c->type->age) { + if (c->type->age(c) == 0) { + result = AT_AGE_REMOVE; + } + } + if (--c->duration <= 0) { + result = AT_AGE_REMOVE; + } } - if (c->type->age) { - result = c->type->age(c); - } - if (result != 0) { - c->duration = 0; - } - else if (c->duration != INT_MAX) { - c->duration = MAX(0, c->duration - 1); - } - return (c->duration > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; + return result; } void destroy_curse(curse * c) @@ -308,6 +305,7 @@ void ct_register(const curse_type * ct) unsigned int hash = tolower(ct->cname[0]) & 0xFF; selist **ctlp = cursetypes + hash; + assert(ct->age==NULL || (ct->flags&CURSE_NOAGE) == 0); selist_set_insert(ctlp, (void *)ct, NULL); ++ct_changes; } diff --git a/src/spells.c b/src/spells.c index b1e5c0591..0c6bb2722 100644 --- a/src/spells.c +++ b/src/spells.c @@ -2079,7 +2079,6 @@ static int sp_homestone(castorder * co) cmistake(mage, co->order, 206, MSG_MAGIC); return 0; } - c_setflag(c, CURSE_NOAGE | CURSE_ONLYONE); /* Magieresistenz der Burg erhoeht sich um 50% */ effect = 50.0F; diff --git a/src/spells/buildingcurse.c b/src/spells/buildingcurse.c index 158383fd2..9c87aaba4 100644 --- a/src/spells/buildingcurse.c +++ b/src/spells/buildingcurse.c @@ -76,7 +76,7 @@ CURSETYP_NORM, 0, M_SUMEFFECT, cinfo_magicrunes /* Heimstein-Zauber */ static struct curse_type ct_magicwalls = { "magicwalls", -CURSETYP_NORM, 0, NO_MERGE, cinfo_building +CURSETYP_NORM, CURSE_ONLYONE|CURSE_NOAGE, NO_MERGE, cinfo_building }; /* Feste Mauer - Präkampfzauber, wirkt nur 1 Runde */ diff --git a/src/spells/flyingship.c b/src/spells/flyingship.c index 2057b72e0..eac2043dd 100644 --- a/src/spells/flyingship.c +++ b/src/spells/flyingship.c @@ -149,10 +149,8 @@ bool flying_ship(const ship * sh) static curse *shipcurse_flyingship(ship * sh, unit * mage, double power, int duration) { curse *c; - const curse_type *ct_flyingship = ct_find("flyingship"); - assert(ct_flyingship); if (sh->attribs) { - if (curse_active(get_curse(sh->attribs, ct_flyingship))) { + if (curse_active(get_curse(sh->attribs, &ct_flyingship))) { return NULL; } if (is_cursed(sh->attribs, C_SHIP_SPEEDUP, 0)) { @@ -160,7 +158,7 @@ static curse *shipcurse_flyingship(ship * sh, unit * mage, double power, int dur } } /* mit C_SHIP_NODRIFT haben wir kein Problem */ - c = create_curse(mage, &sh->attribs, ct_flyingship, power, duration, 0.0, 0); + c = create_curse(mage, &sh->attribs, &ct_flyingship, power, duration, 0.0, 0); if (c) { c->data.v = sh; if (c->duration > 0) {