From 43a28f04fa3ddf4b612f58c25a896293a01567e3 Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 15 Oct 2011 02:28:57 +0300 Subject: mobv2 --- src/mapblockobject.cpp | 939 ------------------------------------------------- 1 file changed, 939 deletions(-) delete mode 100644 src/mapblockobject.cpp (limited to 'src/mapblockobject.cpp') diff --git a/src/mapblockobject.cpp b/src/mapblockobject.cpp deleted file mode 100644 index 071a14b0c..000000000 --- a/src/mapblockobject.cpp +++ /dev/null @@ -1,939 +0,0 @@ -/* -Minetest-c55 -Copyright (C) 2010 celeron55, Perttu Ahola - -This program 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; either version 2 of the License, or -(at your option) any later version. - -This program 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -// This file contains the DEPRECATED MapBlockObject system - -#include "mapblockobject.h" -#include "mapblock.h" -// For object wrapping -#include "map.h" -#include "inventory.h" -#include "utility.h" -#include "mapblock.h" - -/* - MapBlockObject -*/ - -// This is here because it uses the MapBlock -v3f MapBlockObject::getAbsolutePos() -{ - if(m_block == NULL) - return m_pos; - - // getPosRelative gets nodepos relative to map origin - v3f blockpos = intToFloat(m_block->getPosRelative(), BS); - return blockpos + m_pos; -} - -void MapBlockObject::setBlockChanged() -{ - if(m_block) - m_block->setChangedFlag(); -} - -/* - MovingObject -*/ - -v3f MovingObject::getAbsoluteShowPos() -{ - if(m_block == NULL) - return m_pos; - - // getPosRelative gets nodepos relative to map origin - v3f blockpos = intToFloat(m_block->getPosRelative(), BS); - return blockpos + m_showpos; -} - -void MovingObject::move(float dtime, v3f acceleration) -{ - DSTACKF("%s: typeid=%i, pos=(%f,%f,%f), speed=(%f,%f,%f)" - ", dtime=%f, acc=(%f,%f,%f)", - __FUNCTION_NAME, - getTypeId(), - m_pos.X, m_pos.Y, m_pos.Z, - m_speed.X, m_speed.Y, m_speed.Z, - dtime, - acceleration.X, acceleration.Y, acceleration.Z - ); - - v3s16 oldpos_i = floatToInt(m_pos, BS); - - if(m_block->isValidPosition(oldpos_i) == false) - { - // Should have wrapped, cancelling further movement. - return; - } - - // No collisions if there is no collision box - if(m_collision_box == NULL) - { - m_speed += dtime * acceleration; - m_pos += m_speed * dtime; - return; - } - - // Set insane speed to zero - // Otherwise there will be divides by zero and other silly stuff - if(m_speed.getLength() > 1000.0*BS) - m_speed = v3f(0,0,0); - - // Limit speed to a reasonable value - float speed_limit = 20.0*BS; - if(m_speed.getLength() > speed_limit) - m_speed = m_speed * (speed_limit / m_speed.getLength()); - - v3f position = m_pos; - v3f oldpos = position; - - /*std::cout<<"oldpos_i=("< 0.001) - dtime_max_increment = 0.05*BS / speedlength; - else - dtime_max_increment = 0.5; - - m_touching_ground = false; - - u32 loopcount = 0; - do - { - loopcount++; - - f32 dtime_part; - if(dtime > dtime_max_increment) - dtime_part = dtime_max_increment; - else - dtime_part = dtime; - dtime -= dtime_part; - - // Begin of dtime limited code - - m_speed += acceleration * dtime_part; - position += m_speed * dtime_part; - - /* - Collision detection - */ - - v3s16 pos_i = floatToInt(position, BS); - - // The loop length is limited to the object moving a distance - f32 d = (float)BS * 0.15; - - core::aabbox3d objectbox( - m_collision_box->MinEdge + position, - m_collision_box->MaxEdge + position - ); - - core::aabbox3d objectbox_old( - m_collision_box->MinEdge + oldpos, - m_collision_box->MaxEdge + oldpos - ); - - //TODO: Get these ranges from somewhere - for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++) - for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++) - for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++) - { - try{ - MapNode n = m_block->getNodeParent(v3s16(x,y,z)); - if(content_features(n).walkable == false) - continue; - } - catch(InvalidPositionException &e) - { - // Doing nothing here will block the object from - // walking over map borders - } - - core::aabbox3d nodebox = getNodeBox(v3s16(x,y,z), BS); - - // See if the object is touching ground - if( - fabs(nodebox.MaxEdge.Y-objectbox.MinEdge.Y) < d - && nodebox.MaxEdge.X-d > objectbox.MinEdge.X - && nodebox.MinEdge.X+d < objectbox.MaxEdge.X - && nodebox.MaxEdge.Z-d > objectbox.MinEdge.Z - && nodebox.MinEdge.Z+d < objectbox.MaxEdge.Z - ){ - m_touching_ground = true; - } - - if(objectbox.intersectsWithBox(nodebox)) - { - - v3f dirs[3] = { - v3f(0,0,1), // back - v3f(0,1,0), // top - v3f(1,0,0), // right - }; - for(u16 i=0; i<3; i++) - { - f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]); - f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]); - f32 playermax = objectbox.MaxEdge.dotProduct(dirs[i]); - f32 playermin = objectbox.MinEdge.dotProduct(dirs[i]); - f32 playermax_old = objectbox_old.MaxEdge.dotProduct(dirs[i]); - f32 playermin_old = objectbox_old.MinEdge.dotProduct(dirs[i]); - - bool main_edge_collides = - ((nodemax > playermin && nodemax <= playermin_old + d - && m_speed.dotProduct(dirs[i]) < 0) - || - (nodemin < playermax && nodemin >= playermax_old - d - && m_speed.dotProduct(dirs[i]) > 0)); - - bool other_edges_collide = true; - for(u16 j=0; j<3; j++) - { - if(j == i) - continue; - f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]); - f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]); - f32 playermax = objectbox.MaxEdge.dotProduct(dirs[j]); - f32 playermin = objectbox.MinEdge.dotProduct(dirs[j]); - if(!(nodemax - d > playermin && nodemin + d < playermax)) - { - other_edges_collide = false; - break; - } - } - - if(main_edge_collides && other_edges_collide) - { - m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i]; - position -= position.dotProduct(dirs[i]) * dirs[i]; - position += oldpos.dotProduct(dirs[i]) * dirs[i]; - } - - } - - } // if(objectbox.intersectsWithBox(nodebox)) - } // for y - - } // End of dtime limited loop - while(dtime > 0.001); - - m_pos = position; -} - -void MovingObject::simpleMove(float dtime) -{ - m_pos_animation_time_counter += dtime; - m_pos_animation_counter += dtime; - v3f movevector = m_pos - m_oldpos; - f32 moveratio; - if(m_pos_animation_time < 0.001) - moveratio = 1.0; - else - moveratio = m_pos_animation_counter / m_pos_animation_time; - if(moveratio > 1.5) - moveratio = 1.5; - m_showpos = m_oldpos + movevector * moveratio; -} - -#ifndef SERVER -/* - RatObject -*/ -void RatObject::addToScene(scene::ISceneManager *smgr) -{ - if(m_node != NULL) - return; - - video::IVideoDriver* driver = smgr->getVideoDriver(); - - scene::SMesh *mesh = new scene::SMesh(); - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); - video::S3DVertex vertices[4] = - { - video::S3DVertex(-BS/2,-BS/4,0, 0,0,0, c, 0,1), - video::S3DVertex(BS/2,-BS/4,0, 0,0,0, c, 1,1), - video::S3DVertex(BS/2,BS/4,0, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,BS/4,0, 0,0,0, c, 0,0), - }; - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); - buf->getMaterial().setTexture - (0, driver->getTexture(getTexturePath("rat.png").c_str())); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - m_node = smgr->addMeshSceneNode(mesh, NULL); - mesh->drop(); - updateNodePos(); -} -#endif - -/* - ItemObject -*/ -#ifndef SERVER -void ItemObject::addToScene(scene::ISceneManager *smgr) -{ - if(m_node != NULL) - return; - - //video::IVideoDriver* driver = smgr->getVideoDriver(); - - // Get image of item for showing - video::ITexture *texture = getItemImage(); - - /* - Create a mesh - */ - - scene::SMesh *mesh = new scene::SMesh(); - { - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); - video::S3DVertex vertices[4] = - { - /*video::S3DVertex(BS/2,-BS/2,0, 0,0,0, c, 0,1), - video::S3DVertex(-BS/2,-BS/2,0, 0,0,0, c, 1,1), - video::S3DVertex(-BS/2,BS/2,0, 0,0,0, c, 1,0), - video::S3DVertex(BS/2,BS/2,0, 0,0,0, c, 0,0),*/ - video::S3DVertex(BS/3,-BS/2,0, 0,0,0, c, 0,1), - video::S3DVertex(-BS/3,-BS/2,0, 0,0,0, c, 1,1), - video::S3DVertex(-BS/3,-BS/2+BS*2/3,0, 0,0,0, c, 1,0), - video::S3DVertex(BS/3,-BS/2+BS*2/3,0, 0,0,0, c, 0,0), - }; - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); - buf->getMaterial().setTexture(0, texture); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - } - m_node = smgr->addMeshSceneNode(mesh, NULL); - // Set it to use the materials of the meshbuffers directly. - // This is needed for changing the texture in the future - ((scene::IMeshSceneNode*)m_node)->setReadOnlyMaterials(true); - mesh->drop(); - - updateSceneNode(); -} - -video::ITexture * ItemObject::getItemImage() -{ - /* - Create an inventory item to see what is its image - */ - video::ITexture *texture = NULL; - InventoryItem *item = createInventoryItem(); - if(item) - texture = item->getImage(); - if(item) - delete item; - return texture; -} - -#endif - -InventoryItem * ItemObject::createInventoryItem() -{ - try{ - std::istringstream is(m_itemstring, std::ios_base::binary); - InventoryItem *item = InventoryItem::deSerialize(is); - dstream<<__FUNCTION_NAME<<": m_itemstring=\"" - < item="<getVideoDriver(); - - // Attach a simple mesh to the player for showing an image - scene::SMesh *mesh = new scene::SMesh(); - { // Front - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); - video::S3DVertex vertices[4] = - { - video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1), - video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1), - video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0), - video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0), - }; - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); - buf->getMaterial().setTexture(0, driver->getTexture(getTexturePath("player.png").c_str())); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - //buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - } - { // Back - scene::IMeshBuffer *buf = new scene::SMeshBuffer(); - video::SColor c(255,255,255,255); - video::S3DVertex vertices[4] = - { - video::S3DVertex(BS/2,0,0, 0,0,0, c, 1,1), - video::S3DVertex(-BS/2,0,0, 0,0,0, c, 0,1), - video::S3DVertex(-BS/2,BS*2,0, 0,0,0, c, 0,0), - video::S3DVertex(BS/2,BS*2,0, 0,0,0, c, 1,0), - }; - u16 indices[] = {0,1,2,2,3,0}; - buf->append(vertices, 4, indices, 6); - // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); - //buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); - buf->getMaterial().setTexture(0, driver->getTexture(getTexturePath("player_back.png").c_str())); - buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); - buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); - buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; - // Add to mesh - mesh->addMeshBuffer(buf); - buf->drop(); - } - - m_node = smgr->addMeshSceneNode(mesh, NULL); - mesh->drop(); - updateNodePos(); -} -#endif - -/* - MapBlockObjectList -*/ - -MapBlockObjectList::MapBlockObjectList(MapBlock *block): - m_block(block) -{ - m_mutex.Init(); -} - -MapBlockObjectList::~MapBlockObjectList() -{ - clear(); -} - -/* - The serialization format: - [0] u16 number of entries - [2] entries (id, typeId, parameters) -*/ - -void MapBlockObjectList::serialize(std::ostream &os, u8 version) -{ - JMutexAutoLock lock(m_mutex); - - u8 buf[2]; - writeU16(buf, m_objects.size()); - os.write((char*)buf, 2); - - for(core::map::Iterator - i = m_objects.getIterator(); - i.atEnd() == false; i++) - { - i.getNode()->getValue()->serialize(os, version); - } -} - -void MapBlockObjectList::update(std::istream &is, u8 version, - scene::ISceneManager *smgr, u32 daynight_ratio) -{ - JMutexAutoLock lock(m_mutex); - - /* - Collect all existing ids to a set. - - As things are updated, they are removed from this. - - All remaining ones are deleted. - */ - core::map ids_to_delete; - for(core::map::Iterator - i = m_objects.getIterator(); - i.atEnd() == false; i++) - { - ids_to_delete.insert(i.getNode()->getKey(), true); - } - - u8 buf[6]; - - is.read((char*)buf, 2); - u16 count = readU16(buf); - - for(u16 i=0; i::Node *n; - n = m_objects.find(id); - // If no entry is found for id - if(n == NULL) - { - // Insert dummy pointer node - m_objects.insert(id, NULL); - // Get node - n = m_objects.find(id); - // A new object will be created at this node - create_new = true; - } - // If type_id differs - else if(n->getValue()->getTypeId() != type_id) - { - // Delete old object - delete n->getValue(); - // A new object will be created at this node - create_new = true; - } - - MapBlockObject *obj = NULL; - - if(create_new) - { - /*dstream<<"MapBlockObjectList adding new object" - " id="<addToScene(smgr, daynight_ratio); - obj->addToScene(smgr); - - n->setValue(obj); - } - else - { - obj = n->getValue(); - obj->updatePos(pos); - /*if(daynight_ratio != m_last_update_daynight_ratio) - { - obj->removeFromScene(); - obj->addToScene(smgr, daynight_ratio); - }*/ - } - - // Now there is an object in obj. - // Update it. - - obj->update(is, version); - - /* - Update light on client - */ - if(smgr != NULL) - { - u8 light = LIGHT_MAX; - try{ - v3s16 relpos_i = floatToInt(obj->m_pos, BS); - MapNode n = m_block->getNodeParent(relpos_i); - light = n.getLightBlend(daynight_ratio); - } - catch(InvalidPositionException &e) {} - obj->updateLight(light); - } - - // Remove from deletion list - if(ids_to_delete.find(id) != NULL) - ids_to_delete.remove(id); - } - - // Delete all objects whose ids_to_delete remain in ids_to_delete - for(core::map::Iterator - i = ids_to_delete.getIterator(); - i.atEnd() == false; i++) - { - s16 id = i.getNode()->getKey(); - - /*dstream<<"MapBlockObjectList deleting object" - " id="<removeFromScene(); - delete obj; - m_objects.remove(id); - } - - m_last_update_daynight_ratio = daynight_ratio; -} - -s16 MapBlockObjectList::getFreeId() throw(ContainerFullException) -{ - s16 id = 0; - for(;;) - { - if(m_objects.find(id) == NULL) - return id; - if(id == 32767) - throw ContainerFullException - ("MapBlockObjectList doesn't fit more objects"); - id++; - } -} - -void MapBlockObjectList::add(MapBlockObject *object) - throw(ContainerFullException, AlreadyExistsException) -{ - if(object == NULL) - { - dstream<<"MapBlockObjectList::add(): NULL object"<m_id == -1) - { - object->m_id = getFreeId(); - } - - if(m_objects.find(object->m_id) != NULL) - { - dstream<<"MapBlockObjectList::add(): " - "object with same id already exists"<m_block = m_block; - - /*v3f p = object->m_pos; - dstream<<"MapBlockObjectList::add(): " - <<"m_block->getPos()=(" - <getPos().X<<"," - <getPos().Y<<"," - <getPos().Z<<")" - <<" inserting object with id="<m_id - <<" pos=" - <<"("<m_id, object); -} - -void MapBlockObjectList::clear() -{ - JMutexAutoLock lock(m_mutex); - - for(core::map::Iterator - i = m_objects.getIterator(); - i.atEnd() == false; i++) - { - MapBlockObject *obj = i.getNode()->getValue(); - //FIXME: This really shouldn't be NULL at any time, - // but this condition was added because it was. - if(obj != NULL) - { - obj->removeFromScene(); - delete obj; - } - } - - m_objects.clear(); -} - -void MapBlockObjectList::remove(s16 id) -{ - JMutexAutoLock lock(m_mutex); - - core::map::Node *n; - n = m_objects.find(id); - if(n == NULL) - return; - - n->getValue()->removeFromScene(); - delete n->getValue(); - m_objects.remove(id); -} - -MapBlockObject * MapBlockObjectList::get(s16 id) -{ - core::map::Node *n; - n = m_objects.find(id); - if(n == NULL) - return NULL; - else - return n->getValue(); -} - -void MapBlockObjectList::step(float dtime, bool server, u32 daynight_ratio) -{ - DSTACK(__FUNCTION_NAME); - - JMutexAutoLock lock(m_mutex); - - core::map ids_to_delete; - - { - DSTACKF("%s: stepping objects", __FUNCTION_NAME); - - for(core::map::Iterator - i = m_objects.getIterator(); - i.atEnd() == false; i++) - { - MapBlockObject *obj = i.getNode()->getValue(); - - DSTACKF("%s: stepping object type %i", __FUNCTION_NAME, - obj->getTypeId()); - - if(server) - { - // Update light - u8 light = LIGHT_MAX; - try{ - v3s16 relpos_i = floatToInt(obj->m_pos, BS); - MapNode n = m_block->getNodeParent(relpos_i); - light = n.getLightBlend(daynight_ratio); - } - catch(InvalidPositionException &e) {} - obj->updateLight(light); - - bool to_delete = obj->serverStep(dtime, daynight_ratio); - - if(to_delete) - ids_to_delete.insert(obj->m_id, true); - } - else - { - obj->clientStep(dtime); - } - } - } - - { - DSTACKF("%s: deleting objects", __FUNCTION_NAME); - - // Delete objects in delete queue - for(core::map::Iterator - i = ids_to_delete.getIterator(); - i.atEnd() == false; i++) - { - s16 id = i.getNode()->getKey(); - - MapBlockObject *obj = m_objects[id]; - obj->removeFromScene(); - delete obj; - m_objects.remove(id); - } - } - - /* - Wrap objects on server - */ - - if(server == false) - return; - - { - DSTACKF("%s: object wrap loop", __FUNCTION_NAME); - - for(core::map::Iterator - i = m_objects.getIterator(); - i.atEnd() == false; i++) - { - MapBlockObject *obj = i.getNode()->getValue(); - - v3s16 pos_i = floatToInt(obj->m_pos, BS); - - if(m_block->isValidPosition(pos_i)) - { - // No wrap - continue; - } - - bool impossible = wrapObject(obj); - - if(impossible) - { - // No wrap - continue; - } - - // Restart find - i = m_objects.getIterator(); - } - } -} - -bool MapBlockObjectList::wrapObject(MapBlockObject *object) -{ - DSTACK(__FUNCTION_NAME); - - // No lock here; this is called so that the lock is already locked. - //JMutexAutoLock lock(m_mutex); - - assert(object->m_block == m_block); - assert(m_objects.find(object->m_id) != NULL); - assert(m_objects[object->m_id] == object); - - Map *map = m_block->getParent(); - - // Calculate blockpos on map - v3s16 oldblock_pos_i_on_map = m_block->getPosRelative(); - v3f pos_f_on_oldblock = object->m_pos; - v3s16 pos_i_on_oldblock = floatToInt(pos_f_on_oldblock, BS); - v3s16 pos_i_on_map = pos_i_on_oldblock + oldblock_pos_i_on_map; - v3s16 pos_blocks_on_map = getNodeBlockPos(pos_i_on_map); - - // Get new block - MapBlock *newblock; - try{ - newblock = map->getBlockNoCreate(pos_blocks_on_map); - } - catch(InvalidPositionException &e) - { - // Couldn't find block -> not wrapping - /*dstream<<"WARNING: Wrapping object not possible: " - <<"could not find new block" - <<"("<getPosRelative(); - v3f newblock_pos_f_on_map = intToFloat(newblock_pos_i_on_map, BS); - v3f pos_f_on_newblock = pos_f_on_oldblock - - newblock_pos_f_on_map + oldblock_pos_f_on_map; - - // Remove object from this block - m_objects.remove(object->m_id); - - // Add object to new block - object->m_pos = pos_f_on_newblock; - object->m_id = -1; - object->m_block = NULL; - newblock->addObject(object); - - //dstream<<"NOTE: Wrapped object"< &dest) -{ - for(core::map::Iterator - i = m_objects.getIterator(); - i.atEnd() == false; i++) - { - MapBlockObject *obj = i.getNode()->getValue(); - - f32 d = (obj->getRelativeShowPos() - origin).getLength(); - - if(d > max_d) - continue; - - DistanceSortedObject dso(obj, d); - - dest.push_back(dso); - } -} - -//END -- cgit v1.2.3