From a478b9be4e52f6a374d521565c55988cbf80c015 Mon Sep 17 00:00:00 2001 From: Bardur Arantsson Date: Fri, 15 Feb 2019 19:20:25 +0100 Subject: Move 'effects' global into Game struct We change the type to std::vector<> while we're at it and make cave_type::effect to a boost::optional<> --- src/cave.cc | 48 +++++----- src/cave.hpp | 4 +- src/cave_type.hpp | 3 +- src/dungeon.cc | 262 +++++++++++++++++++++++++++++++++------------------- src/effect_type.hpp | 14 +-- src/game.hpp | 6 ++ src/generate.cc | 67 ++++++-------- src/init2.cc | 16 +--- src/loadsave.cc | 39 +++++++- src/spells1.cc | 9 +- src/variable.cc | 1 - src/variable.hpp | 1 - 12 files changed, 283 insertions(+), 187 deletions(-) (limited to 'src') diff --git a/src/cave.cc b/src/cave.cc index d0ce1045..5ec48cf3 100644 --- a/src/cave.cc +++ b/src/cave.cc @@ -836,6 +836,7 @@ static void map_info_layer1( { auto const &st_info = game->edit_data.st_info; auto const &f_info = game->edit_data.f_info; + auto const &lasting_effects = game->lasting_effects; char c; byte a; @@ -880,9 +881,9 @@ static void map_info_layer1( if (apply_effects) { /* Special terrain effect */ - if (c_ptr->effect) + if (auto effect_idx = c_ptr->maybe_effect) { - a = spell_color(effects[c_ptr->effect].type); + a = spell_color(lasting_effects[*effect_idx].type); } /* Multi-hued attr */ @@ -4152,33 +4153,30 @@ int is_quest(int level) } -/* - * handle spell effects +/** + * Create a new lasting effect. */ -int effect_pop() -{ - int i; - - for (i = 1; i < MAX_EFFECTS; i++) - if (!effects[i].time) - return i; - return -1; -} - -int new_effect(int type, int dam, int time, int cy, int cx, int rad, s32b flags) +boost::optional new_effect(int type, int dam, int time, int cy, int cx, int rad, s32b flags) { - int i; + auto &lasting_effects = game->lasting_effects; - if ((i = effect_pop()) == -1) return -1; + // Limit to 128 effects at most. + if (lasting_effects.size() >= 128) + { + return boost::none; + } - effects[i].type = type; - effects[i].dam = dam; - effects[i].time = time; - effects[i].flags = flags; - effects[i].cx = cx; - effects[i].cy = cy; - effects[i].rad = rad; - return i; + effect_type effect; + effect.type = type; + effect.dam = dam; + effect.time = time; + effect.flags = flags; + effect.cx = cx; + effect.cy = cy; + effect.rad = rad; + + lasting_effects.push_back(effect); + return lasting_effects.size() - 1; } /** diff --git a/src/cave.hpp b/src/cave.hpp index ce1631a1..ed352abc 100644 --- a/src/cave.hpp +++ b/src/cave.hpp @@ -4,6 +4,8 @@ #include "cave_type_fwd.hpp" #include "object_type_fwd.hpp" +#include + int distance(int y1, int x1, int y2, int x2); bool_ los(int y1, int x1, int y2, int x2); bool_ cave_valid_bold(int y, int x); @@ -40,7 +42,7 @@ void disturb(); void disturb_on_state(); void disturb_on_other(); int is_quest(int level); -int new_effect(int type, int dam, int time, int cy, int cx, int rad, s32b flags); +boost::optional new_effect(int type, int dam, int time, int cy, int cx, int rad, s32b flags); bool cave_floor_bold(int y, int x); bool cave_floor_grid(cave_type const *c); bool cave_plain_floor_bold(int y, int x); diff --git a/src/cave_type.hpp b/src/cave_type.hpp index 08e6002f..97bb83e2 100644 --- a/src/cave_type.hpp +++ b/src/cave_type.hpp @@ -2,6 +2,7 @@ #include "h-basic.h" +#include #include #include @@ -50,7 +51,7 @@ struct cave_type byte cost = 0; /* Hack -- cost of flowing */ byte when = 0; /* Hack -- when cost was computed */ - s16b effect = 0; /* The lasting effects */ + boost::optional maybe_effect { }; /* The lasting effects */ /** * @brief wipe the object's state diff --git a/src/dungeon.cc b/src/dungeon.cc index b99738e7..a659ace5 100644 --- a/src/dungeon.cc +++ b/src/dungeon.cc @@ -842,15 +842,15 @@ static bool is_light_safe(object_type const *o_ptr) */ static void process_lasting_effects() { + auto &lasting_effects = game->lasting_effects; + for (int j = 0; j < cur_hgt - 1; j++) { for (int i = 0; i < cur_wid - 1; i++) { - int e = cave[j][i].effect; - - if (e) + if (auto ei = cave[j][i].maybe_effect) { - effect_type *e_ptr = &effects[e]; + effect_type *e_ptr = &lasting_effects[*ei]; if (e_ptr->time) { @@ -860,17 +860,19 @@ static void process_lasting_effects() } else { - cave[j][i].effect = 0; + cave[j][i].maybe_effect = boost::none; } if ((e_ptr->flags & EFF_WAVE) && !(e_ptr->flags & EFF_LAST)) { if (distance(e_ptr->cy, e_ptr->cx, j, i) < e_ptr->rad - 1) - cave[j][i].effect = 0; + { + cave[j][i].maybe_effect = boost::none; + } } else if ((e_ptr->flags & EFF_STORM) && !(e_ptr->flags & EFF_LAST)) { - cave[j][i].effect = 0; + cave[j][i].maybe_effect = boost::none; } lite_spot(j, i); @@ -878,152 +880,220 @@ static void process_lasting_effects() } } - /* Reduce & handle effects */ - for (int i = 0; i < MAX_EFFECTS; i++) + // Filter out any expired effects + lasting_effects.erase( + std::remove_if( + std::begin(lasting_effects), + std::end(lasting_effects), + [](auto effect) { return effect.time == 0; }), + std::end(lasting_effects)); + + // Reduce & handle effects + for (std::size_t i = 0; i < lasting_effects.size(); i++) { - /* Skip empty slots */ - if (effects[i].time == 0) continue; + effect_type &e = lasting_effects[i]; - /* Reduce duration */ - effects[i].time--; + // Reduce duration + e.time--; - /* Creates a "wave" effect*/ - if (effects[i].flags & EFF_WAVE) + // Set up the effect in the dungeon. + if (e.flags & EFF_WAVE) { - effect_type *e_ptr = &effects[i]; - int x, y, z; + // Expand the wave front + e.rad++; - e_ptr->rad++; - - /* What a frelling ugly line of ifs ... */ - if (effects[i].flags & EFF_DIR8) - for (y = e_ptr->cy - e_ptr->rad, z = 0; y <= e_ptr->cy; y++, z++) + // Check direction + if (e.flags & EFF_DIR8) + { + for (int y = e.cy - e.rad, z = 0; y <= e.cy; y++, z++) { - for (x = e_ptr->cx - (e_ptr->rad - z); x <= e_ptr->cx + (e_ptr->rad - z); x++) + for (int x = e.cx - (e.rad - z); x <= e.cx + (e.rad - z); x++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } - else if (effects[i].flags & EFF_DIR2) - for (y = e_ptr->cy, z = e_ptr->rad; y <= e_ptr->cy + e_ptr->rad; y++, z--) + } + else if (e.flags & EFF_DIR2) + { + for (int y = e.cy, z = e.rad; y <= e.cy + e.rad; y++, z--) { - for (x = e_ptr->cx - (e_ptr->rad - z); x <= e_ptr->cx + (e_ptr->rad - z); x++) + for (int x = e.cx - (e.rad - z); x <= e.cx + (e.rad - z); x++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } - else if (effects[i].flags & EFF_DIR6) - for (x = e_ptr->cx, z = e_ptr->rad; x <= e_ptr->cx + e_ptr->rad; x++, z--) + } + else if (e.flags & EFF_DIR6) + { + for (int x = e.cx, z = e.rad; x <= e.cx + e.rad; x++, z--) { - for (y = e_ptr->cy - (e_ptr->rad - z); y <= e_ptr->cy + (e_ptr->rad - z); y++) + for (int y = e.cy - (e.rad - z); y <= e.cy + (e.rad - z); y++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } - else if (effects[i].flags & EFF_DIR4) - for (x = e_ptr->cx - e_ptr->rad, z = 0; x <= e_ptr->cx; x++, z++) + } + else if (e.flags & EFF_DIR4) + { + for (int x = e.cx - e.rad, z = 0; x <= e.cx; x++, z++) { - for (y = e_ptr->cy - (e_ptr->rad - z); y <= e_ptr->cy + (e_ptr->rad - z); y++) + for (int y = e.cy - (e.rad - z); y <= e.cy + (e.rad - z); y++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } - else if (effects[i].flags & EFF_DIR9) - for (y = e_ptr->cy - e_ptr->rad; y <= e_ptr->cy; y++) + } + else if (e.flags & EFF_DIR9) + { + for (int y = e.cy - e.rad; y <= e.cy; y++) { - for (x = e_ptr->cx; x <= e_ptr->cx + e_ptr->rad; x++) + for (int x = e.cx; x <= e.cx + e.rad; x++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } - else if (effects[i].flags & EFF_DIR1) - for (y = e_ptr->cy; y <= e_ptr->cy + e_ptr->rad; y++) + } + else if (e.flags & EFF_DIR1) + { + for (int y = e.cy; y <= e.cy + e.rad; y++) { - for (x = e_ptr->cx - e_ptr->rad; x <= e_ptr->cx; x++) + for (int x = e.cx - e.rad; x <= e.cx; x++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } - else if (effects[i].flags & EFF_DIR7) - for (y = e_ptr->cy - e_ptr->rad; y <= e_ptr->cy; y++) + } + else if (e.flags & EFF_DIR7) + { + for (int y = e.cy - e.rad; y <= e.cy; y++) { - for (x = e_ptr->cx - e_ptr->rad; x <= e_ptr->cx; x++) + for (int x = e.cx - e.rad; x <= e.cx; x++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } - else if (effects[i].flags & EFF_DIR3) - for (y = e_ptr->cy; y <= e_ptr->cy + e_ptr->rad; y++) + } + else if (e.flags & EFF_DIR3) + { + for (int y = e.cy; y <= e.cy + e.rad; y++) { - for (x = e_ptr->cx; x <= e_ptr->cx + e_ptr->rad; x++) + for (int x = e.cx; x <= e.cx + e.rad; x++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } + } else - for (y = e_ptr->cy - e_ptr->rad; y <= e_ptr->cy + e_ptr->rad; y++) + { + for (int y = e.cy - e.rad; y <= e.cy + e.rad; y++) { - for (x = e_ptr->cx - e_ptr->rad; x <= e_ptr->cx + e_ptr->rad; x++) + for (int x = e.cx - e.rad; x <= e.cx + e.rad; x++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - /* This is *slow* -- pelpel */ - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) == e_ptr->rad)) - cave[y][x].effect = i; + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) == e.rad)) + { + cave[y][x].maybe_effect = i; + } } } + } } - /* Creates a "storm" effect*/ - else if (effects[i].flags & EFF_STORM) + else if (e.flags & EFF_STORM) { - effect_type *e_ptr = &effects[i]; - int x, y; - - e_ptr->cy = p_ptr->py; - e_ptr->cx = p_ptr->px; - for (y = e_ptr->cy - e_ptr->rad; y <= e_ptr->cy + e_ptr->rad; y++) + // Center on player + e.cy = p_ptr->py; + e.cx = p_ptr->px; + // Set up the effect + for (int y = e.cy - e.rad; y <= e.cy + e.rad; y++) { - for (x = e_ptr->cx - e_ptr->rad; x <= e_ptr->cx + e_ptr->rad; x++) + for (int x = e.cx - e.rad; x <= e.cx + e.rad; x++) { - if (!in_bounds(y, x)) continue; + if (!in_bounds(y, x)) + { + continue; + } - if (los(e_ptr->cy, e_ptr->cx, y, x) && - (distance(e_ptr->cy, e_ptr->cx, y, x) <= e_ptr->rad)) + if (los(e.cy, e.cx, y, x) && + (distance(e.cy, e.cx, y, x) <= e.rad)) { - cave[y][x].effect = i; + cave[y][x].maybe_effect = i; lite_spot(y, x); } } diff --git a/src/effect_type.hpp b/src/effect_type.hpp index 8cd638c6..9651d449 100644 --- a/src/effect_type.hpp +++ b/src/effect_type.hpp @@ -7,11 +7,11 @@ */ struct effect_type { - s16b time; /* For how long */ - s16b dam; /* How much damage */ - s16b type; /* Of which type */ - s16b cy; /* Center of the cast*/ - s16b cx; /* Center of the cast*/ - s16b rad; /* Radius -- if needed */ - u32b flags; /* Flags */ + s16b time = 0; /* For how long */ + s16b dam = 0; /* How much damage */ + s16b type = 0; /* Of which type */ + s16b cy = 0; /* Center of the cast*/ + s16b cx = 0; /* Center of the cast*/ + s16b rad = 0; /* Radius -- if needed */ + u32b flags = 0; /* Flags */ }; diff --git a/src/game.hpp b/src/game.hpp index d674d8cb..937c4190 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -4,6 +4,7 @@ #include "alloc.hpp" #include "birther.hpp" +#include "effect_type.hpp" #include "game_edit_data.hpp" #include "grid.hpp" #include "h-basic.h" @@ -110,6 +111,11 @@ struct Game { */ dungeon_flag_set dungeon_flags { }; + /** + * Lasting effects. + */ + std::vector lasting_effects { }; + /** * Generate a special level feeling? */ diff --git a/src/generate.cc b/src/generate.cc index ad7775d4..e5dd0163 100644 --- a/src/generate.cc +++ b/src/generate.cc @@ -8122,6 +8122,29 @@ static void generate_grid_mana() } +/* + * Clear dungeon + */ +static void clear_dungeon() +{ + /* Clear any lasting effects */ + game->lasting_effects.clear(); + + /* Start with a blank cave */ + for (int y = 0; y < MAX_HGT; y++) + { + for (int x = 0; x < MAX_WID; x++) + { + /* Wipe */ + cave[y][x].wipe(); + + /* No features */ + cave_set_feat(y, x, FEAT_PERM_INNER); + } + } +} + + /* * Generates a random dungeon level -RAK- * @@ -8138,7 +8161,7 @@ void generate_cave() auto d_ptr = &d_info[dungeon_type]; int tester_1, tester_2; - int y, x, num, i; + int num, i; bool loaded = false; s16b town_level = 0; @@ -8177,25 +8200,9 @@ void generate_cave() /* Try to load a saved level */ if (auto ext = get_dungeon_save_extension()) { - /* No effects */ - for (i = 0; i < MAX_EFFECTS; i++) - { - effects[i].time = 0; - } - - /* Start with a blank cave */ - for (y = 0; y < MAX_HGT; y++) - { - for (x = 0; x < MAX_WID; x++) - { - /* Wipe */ - cave[y][x].wipe(); - - /* No features */ - cave_set_feat(y, x, FEAT_PERM_INNER); - } - } - + /* Clear */ + clear_dungeon(); + /* Load */ loaded = load_dungeon(*ext); } @@ -8215,24 +8222,8 @@ void generate_cave() const char *why = NULL; - /* No effects */ - for (i = 0; i < MAX_EFFECTS; i++) - { - effects[i].time = 0; - } - - /* Start with a blank cave */ - for (y = 0; y < MAX_HGT; y++) - { - for (x = 0; x < MAX_WID; x++) - { - /* Wipe */ - cave[y][x].wipe(); - - /* No features */ - cave_set_feat(y, x, FEAT_PERM_INNER); - } - } + /* Clear */ + clear_dungeon(); /* XXX XXX XXX XXX */ diff --git a/src/init2.cc b/src/init2.cc index 830b180e..25db5c1f 100644 --- a/src/init2.cc +++ b/src/init2.cc @@ -520,18 +520,12 @@ static void init_basic() */ static errr init_misc() { - int xstart = 0; - int ystart = 0; - int i; - - /*** Prepare the various "bizarre" arrays ***/ - /* Initialise the values */ - process_dungeon_file("misc.txt", &ystart, &xstart, 0, 0, TRUE, FALSE); - - /* Init the spell effects */ - for (i = 0; i < MAX_EFFECTS; i++) - effects[i].time = 0; + { + int xstart = 0; + int ystart = 0; + process_dungeon_file("misc.txt", &ystart, &xstart, 0, 0, TRUE, FALSE); + } /* Initialize timers */ TIMER_INERTIA_CONTROL = diff --git a/src/loadsave.cc b/src/loadsave.cc index 13e590fe..c8d1d45f 100644 --- a/src/loadsave.cc +++ b/src/loadsave.cc @@ -457,6 +457,40 @@ static void do_seed(seed_t *seed, ls_flag_t flag) } } +template +static void do_boost_optional(boost::optional &maybe_v, ls_flag_t flag, F f) +{ + if (flag == ls_flag_t::SAVE) + { + // Size + u32b n = maybe_v + ? 1 + : 0; + do_u32b(&n, flag); + + // Value + if (maybe_v) + { + auto v = *maybe_v; + f(&v, flag); + } + } + + if (flag == ls_flag_t::LOAD) + { + // Size + u32b n; + do_u32b(&n, flag); + + // Value + while (n-- > 0) + { + maybe_v.emplace(); // Default-construct in place + f(&maybe_v.get(), flag); + } + } +} + } // namespace (anonymous) @@ -1275,7 +1309,7 @@ static void do_cave_type(cave_type *c_ptr, ls_flag_t flag) do_s16b(&c_ptr->special2, flag); do_s16b(&c_ptr->inscription, flag); do_byte(&c_ptr->mana, flag); - do_s16b(&c_ptr->effect, flag); + do_boost_optional(c_ptr->maybe_effect, flag, do_s16b); } static void do_grid(ls_flag_t flag) @@ -1506,6 +1540,7 @@ static bool do_monsters(ls_flag_t flag, bool no_companions) static bool_ do_dungeon(ls_flag_t flag, bool no_companions) { auto &dungeon_flags = game->dungeon_flags; + auto &effects = game->lasting_effects; /* Header info */ do_s16b(&dun_level, flag); @@ -1525,7 +1560,7 @@ static bool_ do_dungeon(ls_flag_t flag, bool no_companions) do_s16b(&last_teleportation_y, flag); /* Spell effects */ - do_array("spell effects", flag, effects, MAX_EFFECTS, + do_vector(flag, effects, [](auto effect, auto flag) -> void { do_s16b(&effect->type, flag); do_s16b(&effect->dam, flag); diff --git a/src/spells1.cc b/src/spells1.cc index 7cdabda7..8fc4a23b 100644 --- a/src/spells1.cc +++ b/src/spells1.cc @@ -7898,8 +7898,6 @@ bool_ project(int who, int rad, int y, int x, int dam, int typ, int flg) /* Number of grids in the "blast area" (including the "beam" path) */ int grids = 0; - int effect = 0; - /* Coordinates of the affected grids */ byte gx[1024], gy[1024]; @@ -8220,10 +8218,13 @@ bool_ project(int who, int rad, int y, int x, int dam, int typ, int flg) /* Start with "dist" of zero */ dist = 0; + /* Which effect? */ + boost::optional maybe_effect = boost::none; + /* Effect ? */ if (flg & PROJECT_STAY) { - effect = new_effect(typ, dam, project_time, p_ptr->py, p_ptr->px, rad, project_time_effect); + maybe_effect = new_effect(typ, dam, project_time, p_ptr->py, p_ptr->px, rad, project_time_effect); project_time = 0; project_time_effect = 0; } @@ -8244,7 +8245,7 @@ bool_ project(int who, int rad, int y, int x, int dam, int typ, int flg) /* Effect ? */ if (flg & PROJECT_STAY) { - cave[y][x].effect = effect; + cave[y][x].maybe_effect = maybe_effect; lite_spot(y, x); } } diff --git a/src/variable.cc b/src/variable.cc index 5945a5ba..f2046b89 100644 --- a/src/variable.cc +++ b/src/variable.cc @@ -559,7 +559,6 @@ school_type schools[SCHOOLS_MAX]; */ int project_time = 0; s32b project_time_effect = 0; -effect_type effects[MAX_EFFECTS]; /* * Table of "cli" macros. diff --git a/src/variable.hpp b/src/variable.hpp index 4d50fff4..2af69570 100644 --- a/src/variable.hpp +++ b/src/variable.hpp @@ -169,7 +169,6 @@ extern s16b schools_count; extern school_type schools[SCHOOLS_MAX]; extern int project_time; extern s32b project_time_effect; -extern effect_type effects[MAX_EFFECTS]; extern bool_ automatizer_enabled; extern s16b last_teleportation_y; extern s16b last_teleportation_x; -- cgit v1.2.3