/* Minetest Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser 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. */ #include "inventory.h" #include "serialization.h" #include "debug.h" #include #include "log.h" #include "itemdef.h" #include "util/strfnd.h" #include "content_mapnode.h" // For loading legacy MaterialItems #include "nameidmapping.h" // For loading legacy MaterialItems #include "util/serialize.h" #include "util/string.h" /* ItemStack */ static content_t content_translate_from_19_to_internal(content_t c_from) { for(u32 i=0; i= 0x7f || s[i] == ' ' || s[i] == '\"') return serializeJsonString(s); } return s; } // Parses a string serialized by serializeJsonStringIfNeeded. static std::string deSerializeJsonStringIfNeeded(std::istream &is) { std::ostringstream tmp_os; bool expect_initial_quote = true; bool is_json = false; bool was_backslash = false; for(;;) { char c = is.get(); if(is.eof()) break; if(expect_initial_quote && c == '"') { tmp_os << c; is_json = true; } else if(is_json) { tmp_os << c; if(was_backslash) was_backslash = false; else if(c == '\\') was_backslash = true; else if(c == '"') break; // Found end of string } else { if(c == ' ') { // Found end of word is.unget(); break; } else { tmp_os << c; } } expect_initial_quote = false; } if(is_json) { std::istringstream tmp_is(tmp_os.str(), std::ios::binary); return deSerializeJsonString(tmp_is); } else return tmp_os.str(); } ItemStack::ItemStack(std::string name_, u16 count_, u16 wear_, std::string metadata_, IItemDefManager *itemdef) { name = itemdef->getAlias(name_); count = count_; wear = wear_; metadata = metadata_; if(name.empty() || count == 0) clear(); else if(itemdef->get(name).type == ITEM_TOOL) count = 1; } void ItemStack::serialize(std::ostream &os) const { DSTACK(FUNCTION_NAME); if(empty()) return; // Check how many parts of the itemstring are needed int parts = 1; if(count != 1) parts = 2; if(wear != 0) parts = 3; if(metadata != "") parts = 4; os<= 2) os<<" "<= 3) os<<" "<= 4) os<<" "<>material; u16 materialcount; is>>materialcount; // Convert old materials if(material <= 0xff) material = content_translate_from_19_to_internal(material); if(material > 0xfff) throw SerializationError("Too large material number"); // Convert old id to name NameIdMapping legacy_nimap; content_mapnode_get_name_id_mapping(&legacy_nimap); legacy_nimap.getName(material, name); if(name == "") name = "unknown_block"; if (itemdef) name = itemdef->getAlias(name); count = materialcount; } else if(name == "MaterialItem2") { // Obsoleted on 2011-11-16 u16 material; is>>material; u16 materialcount; is>>materialcount; if(material > 0xfff) throw SerializationError("Too large material number"); // Convert old id to name NameIdMapping legacy_nimap; content_mapnode_get_name_id_mapping(&legacy_nimap); legacy_nimap.getName(material, name); if(name == "") name = "unknown_block"; if (itemdef) name = itemdef->getAlias(name); count = materialcount; } else if(name == "node" || name == "NodeItem" || name == "MaterialItem3" || name == "craft" || name == "CraftItem") { // Obsoleted on 2012-01-07 std::string all; std::getline(is, all, '\n'); // First attempt to read inside "" Strfnd fnd(all); fnd.next("\""); // If didn't skip to end, we have ""s if(!fnd.at_end()){ name = fnd.next("\""); } else { // No luck, just read a word then fnd.start(all); name = fnd.next(" "); } fnd.skip_over(" "); if (itemdef) name = itemdef->getAlias(name); count = stoi(trim(fnd.next(""))); if(count == 0) count = 1; } else if(name == "MBOItem") { // Obsoleted on 2011-10-14 throw SerializationError("MBOItem not supported anymore"); } else if(name == "tool" || name == "ToolItem") { // Obsoleted on 2012-01-07 std::string all; std::getline(is, all, '\n'); // First attempt to read inside "" Strfnd fnd(all); fnd.next("\""); // If didn't skip to end, we have ""s if(!fnd.at_end()){ name = fnd.next("\""); } else { // No luck, just read a word then fnd.start(all); name = fnd.next(" "); } count = 1; // Then read wear fnd.skip_over(" "); if (itemdef) name = itemdef->getAlias(name); wear = stoi(trim(fnd.next(""))); } else { do // This loop is just to allow "break;" { // The real thing // Apply item aliases if (itemdef) name = itemdef->getAlias(name); // Read the count std::string count_str; std::getline(is, count_str, ' '); if(count_str.empty()) { count = 1; break; } else count = stoi(count_str); // Read the wear std::string wear_str; std::getline(is, wear_str, ' '); if(wear_str.empty()) break; else wear = stoi(wear_str); // Read metadata metadata = deSerializeJsonStringIfNeeded(is); // In case fields are added after metadata, skip space here: //std::getline(is, tmp, ' '); //if(!tmp.empty()) // throw SerializationError("Unexpected text after metadata"); } while(false); } if (name.empty() || count == 0) clear(); else if (itemdef && itemdef->get(name).type == ITEM_TOOL) count = 1; } void ItemStack::deSerialize(const std::string &str, IItemDefManager *itemdef) { std::istringstream is(str, std::ios::binary); deSerialize(is, itemdef); } std::string ItemStack::getItemString() const { std::ostringstream os(std::ios::binary); serialize(os); return os.str(); } ItemStack ItemStack::addItem(const ItemStack &newitem_, IItemDefManager *itemdef) { ItemStack newitem = newitem_; // If the item is empty or the position invalid, bail out if(newitem.empty()) { // nothing can be added trivially } // If this is an empty item, it's an easy job. else if(empty()) { *this = newitem; newitem.clear(); } // If item name or metadata differs, bail out else if (name != newitem.name || metadata != newitem.metadata) { // cannot be added } // If the item fits fully, add counter and delete it else if(newitem.count <= freeSpace(itemdef)) { add(newitem.count); newitem.clear(); } // Else the item does not fit fully. Add all that fits and return // the rest. else { u16 freespace = freeSpace(itemdef); add(freespace); newitem.remove(freespace); } return newitem; } bool ItemStack::itemFits(const ItemStack &newitem_, ItemStack *restitem, IItemDefManager *itemdef) const { ItemStack newitem = newitem_; // If the item is empty or the position invalid, bail out if(newitem.empty()) { // nothing can be added trivially } // If this is an empty item, it's an easy job. else if(empty()) { newitem.clear(); } // If item name or metadata differs, bail out else if (name != newitem.name || metadata != newitem.metadata) { // cannot be added } // If the item fits fully, delete it else if(newitem.count <= freeSpace(itemdef)) { newitem.clear(); } // Else the item does not fit fully. Return the rest. // the rest. else { u16 freespace = freeSpace(itemdef); newitem.remove(freespace); } if(restitem) *restitem = newitem; return newitem.empty(); } ItemStack ItemStack::takeItem(u32 takecount) { if(takecount == 0 || count == 0) return ItemStack(); ItemStack result = *this; if(takecount >= count) { // local print=function(t) minetest.log("action", t) minetest.chat_send_all(t) end --pseudoload.lua --responsible for keeping up a database of all rail nodes existant in the world, regardless of whether the mapchunk is loaded. advtrains.trackdb={} --trackdb[tt][y][x][z]={conn1, conn2, rely1, rely2, railheight} --serialization format: --(2byte x)(2byte y)(2byte z)(4bits conn1, 4bits conn2)[(plain rely1)|(plain rely2)|(plain railheight)]\n --[] may be missing if 0,0,0 --load initially --[[ TODO temporary outcomment --delayed since all traintypes need to be registered minetest.after(0, function() for tt, _ in pairs(advtrains.all_traintypes) do local pl_fpath=minetest.get_worldpath().."/advtrains_trackdb_"..tt advtrains.trackdb[tt]={} local file, err = io.open(pl_fpath, "r") if not file then local er=err or "Unknown Error" print("[advtrains]Failed loading advtrains trackdb save file "..er) else --custom format to save memory while true do local xbytes=file:read(2) if not xbytes or #xbytes<2 then break --eof reached end print(xbytes) local ybytes=file:read(2) local zbytes=file:read(2) local x=(string.byte(xbytes[1])-128)*256+(string.byte(xbytes[2])) local y=(string.byte(ybytes[1])-128)*256+(string.byte(ybytes[2])) local z=(string.byte(zbytes[1])-128)*256+(string.byte(zbytes[2])) local conn1=string.byte(file:read(1)) local conn1=string.byte(file:read(1)) if not advtrains.trackdb[tt][y] then advtrains.trackdb[tt][y]={} end if not advtrains.trackdb[tt][y][x] then advtrains.trackdb[tt][y][x]={} end local rest=file.read("*l") if rest~="" then local rely1, rely2, railheight=string.match(rest, "([^|]+)|([^|]+)|([^|]+)") if rely1 and rely2 and railheight then advtrains.trackdb[tt][y][x][z]={ conn1=conn1, conn2=conn2, rely1=rely1, rely2=rely2, railheight=railheight } else advtrains.trackdb[tt][y][x][z]={ conn1=conn1, conn2=conn2 } end else advtrains.trackdb[tt][y][x][z]={ conn1=conn1, conn2=conn2 } end end file:close() end end --end minetest.after end) function advtrains.save_trackdb() for tt, _ in pairs(advtrains.all_traintypes) do local pl_fpath=minetest.get_worldpath().."/advtrains_trackdb_"..tt local file, err = io.open(pl_fpath, "w") if not file then local er=err or "Unknown Error" print("[advtrains]Failed saving advtrains trackdb save file "..er) else --custom format to save memory for y,tyl in pairs(advtrains.trackdb[tt]) do for x,txl in pairs(tyl) do for z,rail in pairs(txl) do print("write "..x.." "..y.." "..z.." "..minetest.serialize(rail)) file:write(string.char(math.floor(x/256)+128)..string.char((x%256))) file:write(string.char(math.floor(y/256)+128)..string.char((y%256))) file:write(string.char(math.floor(z/256)+128)..string.char((z%256))) file:write(string.char(rail.conn1)) file:write(string.char(rail.conn2)) if (rail.rely1 and rail.rely1~=0) or (rail.rely2 and rail.rely2~=0) or (rail.railheight and rail.railheight~=0) then file:write(rail.rely1.."|"..rail.rely2.."|"..rail.railheight) end file:write("\n") end end end file:close() end end end ]]--end temp outcomment advtrains.trackdb={} advtrains.fpath_tdb=minetest.get_worldpath().."/advtrains_trackdb" local file, err = io.open(advtrains.fpath_tdb, "r") if not file then local er=err or "Unknown Error" print("[advtrains]Failed loading advtrains save file "..er) else local tbl = minetest.deserialize(file:read("*a")) if type(tbl) == "table" then advtrains.trackdb=tbl end file:close() end function advtrains.save_trackdb() local datastr = minetest.serialize(advtrains.trackdb) if not datastr then minetest.log("error", "[advtrains] Failed to serialize trackdb data!") return end local file, err = io.open(advtrains.fpath_tdb, "w") if err then return err end file:write(datastr) file:close() end --get_node with pseudoload. --returns: --true, conn1, conn2, rely1, rely2, railheight in case everything's right. --false if it's not a rail or the train does not drive on this rail, but it is loaded or --nil if the node is neither loaded nor in trackdb --the distraction between false and nil will be needed only in special cases.(train initpos) function advtrains.get_rail_info_at(pos, traintype) local node=minetest.get_node_or_nil(pos) if not node then --try raildb local rdp=vector.round(pos) local dbe=(advtrains.trackdb[traintype] and advtrains.trackdb[traintype][rdp.y] and advtrains.trackdb[traintype][rdp.y][rdp.x]