diff --git a/code/modules/maps/reader.dm b/code/modules/maps/reader.dm index cc107abd04..d67b156863 100644 --- a/code/modules/maps/reader.dm +++ b/code/modules/maps/reader.dm @@ -1,325 +1,322 @@ -dmm_suite +/////////////////////////////////////////////////////////////// +//SS13 Optimized Map loader +////////////////////////////////////////////////////////////// - var/debug_file = file("maploader_debug.txt") - load_map(var/dmm_file as file, var/z_offset as num, var/y_offset as num, var/x_offset as num, var/load_speed = 0 as num) - if(!z_offset) - z_offset = world.maxz + 1 +/** + * Construct the model map and control the loading process + * + * WORKING : + * + * 1) Makes an associative mapping of model_keys with model + * e.g aa = /turf/unsimulated/wall{icon_state = "rock"} + * 2) Read the map line by line, parsing the result (using parse_grid) + * + */ +/dmm_suite/load_map(var/dmm_file as file, var/z_offset as num) + if(!z_offset)//what z_level we are creating the map on + z_offset = world.maxz+1 - //Ensure values are sane. - else if(z_offset < 0) - z_offset = abs(z_offset) - else if(!isnum(z_offset)) - z_offset = 0 + var/quote = ascii2text(34) + var/tfile = file2text(dmm_file)//the map file we're creating + var/tfile_len = length(tfile) + var/lpos = 1 // the models definition index - if(x_offset < 0) - x_offset = abs(x_offset) - else if(!isnum(x_offset)) - x_offset = 0 + /////////////////////////////////////////////////////////////////////////////////////// + //first let's map model keys (e.g "aa") to their contents (e.g /turf/space{variables}) + /////////////////////////////////////////////////////////////////////////////////////// + var/list/grid_models = list() + var/key_len = length(copytext(tfile,2,findtext(tfile,quote,2,0)))//the length of the model key (e.g "aa" or "aba") - if(y_offset < 0) - y_offset = abs(y_offset) - else if(!isnum(y_offset)) - y_offset = 0 + //proceed line by line + for(lpos=1; lpos 0) - //Chance out of 100 every tenth of a second. - delay_chance = 1000 / load_speed + //position of the currently processed square + var/zcrd=-1 + var/ycrd=0 + var/xcrd=0 - //String holding a quotation mark. - var/quote = ascii2text(34) + for(var/zpos=findtext(tfile,"\n(1,1,",lpos,0);zpos!=0;zpos=findtext(tfile,"\n(1,1,",zpos+1,0)) //in case there's several maps to load - var/input_file = file2text(dmm_file) - var/input_file_len = length(input_file) + zcrd++ + world.maxz = max(world.maxz, zcrd+z_offset)//create a new z_level if needed - //Stores the contents of each tile model in the map - var/list/grid_models = list() - //Length of the tile model code. e.g. "aaa" is 3 long. - var/key_len = length(copytext(input_file, 2 ,findtext(input_file, quote, 2))) - //The key of the default tile model. (In SS13 this is: "/turf/space,/area") - var/default_key + var/zgrid = copytext(tfile,findtext(tfile,quote+"\n",zpos,0)+2,findtext(tfile,"\n"+quote,zpos,0)+1) //copy the whole map grid + var/z_depth = length(zgrid) - debug_file << " Building turf array." + //if exceeding the world max x or y, increase it + var/x_depth = length(copytext(zgrid,1,findtext(zgrid,"\n",2,0))) + if(world.maxxz_depth) break - //Copy contents of the model into the grid_models list. - var/model_key = copytext(next_line, 2, findtext(input_file, quote, 2)) - var/model_contents = copytext(next_line, findtext(next_line, "=" ) + 3) - if(!default_key && model_contents == "[world.turf],[world.area]") - default_key = model_key - grid_models[model_key] = model_contents - if(prob(delay_chance)) - sleep(1) - - //Co-ordinates of the tile being loaded. - var/z_coordinate = -1 - var/y_coordinate = 0 - var/x_coordinate = 0 - - //Store the - var/y_depth = 0 - - //Iterate through all z-levels to load the tiles. - for(var/z_position = findtext(input_file, "\n(1,1,"); TRUE; z_position = findtext(input_file, "\n(1,1,", z_position + 1)) - //break when there are no more z-levels. - if(z_position == 0) - break - - //Increment the z_coordinate and update the world's borders - z_coordinate++ - world.maxz = max(world.maxz, z_coordinate + z_offset) - - //Here we go! - y_coordinate = 0 - y_depth = 0 - var/z_level = copytext(input_file, \ - findtext(input_file, quote + "\n", z_position) + 2,\ - findtext(input_file, "\n" + quote, z_position) + 1) - - //Iterate through each line, increasing the y_coordinate. - for(var/grid_position = 1; grid_position != 0; grid_position = findtext(z_level, "\n", grid_position) + 1) - //Grab this line of data. - var/grid_line = copytext(z_level, grid_position, findtext(z_level, "\n", grid_position)) - - //Compute the size of the z-levels y axis. - if(!y_depth) - y_depth = length(z_level) / (length(grid_line) + 1) - y_depth += y_offset - if(y_depth != round(y_depth, 1)) - debug_file << " Warning: y_depth is not a round number" - - //And update the worlds variables. - if(world.maxy < y_depth) - world.maxy = y_depth - //The top of the map is the highest "y" co-ordinate, so we start there and iterate downwards - if(!y_coordinate) - y_coordinate = y_depth + 1 - - //Decrement and load this line of the map. - y_coordinate-- - x_coordinate = x_offset - - //Iterate through the line loading the model tile data. - for(var/model_position = 1; model_position <= length(grid_line); model_position += key_len) - x_coordinate++ - - //Find the model key and load that model. - var/model_key = copytext(grid_line, model_position, model_position + key_len) - //If the key is the default one, skip it and save the computation time. - if(model_key == default_key) - continue - - if(world.maxx < x_coordinate) - world.maxx = x_coordinate - parse_grid(grid_models[model_key], x_coordinate, y_coordinate, z_coordinate + z_offset) - - if(prob(delay_chance)) - sleep(1) - - //If we hit the last tile in this z-level, we should break out of the loop. - if(grid_position + length(grid_line) + 1 > length(z_level)) - break - - //Break out of the loop when we hit the end of the file. - if(findtext(input_file, quote + "}", z_position) + 2 >= input_file_len) - break - - - proc/parse_grid(var/model as text, var/x_coordinate as num, var/y_coordinate as num, var/z_coordinate as num) - //Accepts a text string containing a comma separated list of type paths of the - // same construction as those contained in a .dmm file, and instantiates them. - - var/list/text_strings = list() - for(var/index = 1; findtext(model, quote); index++) - /*Loop: Stores quoted portions of text in text_strings, and replaces them with an - index to that list. - - Each iteration represents one quoted section of text. - */ - //Add the next section of quoted text to the list - var/first_quote = findtext(model, quote) - var/second_quote = findtext(model, quote, first_quote + 1) - var/quoted_chunk = copytext(model, first_quote + 1, second_quote) - text_strings += quoted_chunk - //Then remove the quoted section. - model = copytext(model, 1, first_quote) + "~[index]" + copytext(model, second_quote + 1) - - var/debug_output = 0 - //if(x_coordinate == 86 && y_coordinate == 88 && z_coordinate == 7) - // debug_output = 1 - - if(debug_output) - debug_file << " Now debugging turf: [model] ([x_coordinate], [y_coordinate], [z_coordinate])" - - var/next_position = 1 - for(var/data_position = 1, next_position || data_position != 1, data_position = next_position + 1) - next_position = findtext(model, ",/", data_position) - - var/full_def = copytext(model, data_position, next_position) - - if(debug_output) - debug_file << " Current Line: [full_def] -- ([data_position] - [next_position])" - - /*Loop: Identifies each object's data, instantiates it, and reconstitues it's fields. - - Each iteration represents one object's data, including type path and field values. - */ - - //Load the attribute data. - var/attribute_position = findtext(full_def,"{") - var/atom_def = text2path(copytext(full_def, 1, attribute_position)) - - var/list/attributes = list() - if(attribute_position) - full_def = copytext(full_def, attribute_position + 1) - if(debug_output) - debug_file << " Atom Def: [atom_def]" - debug_file << " Parameters: [full_def]" - - var/next_attribute = 1 - for(attribute_position = 1, next_attribute || attribute_position != 1, attribute_position = next_attribute + 1) - next_attribute = findtext(full_def, ";", attribute_position) - - //Loop: Identifies each attribute/value pair, and stores it in attributes[]. - attributes += copytext(full_def, attribute_position, next_attribute) - - //Construct attributes associative list - var/list/fields = list() - for(var/attribute in attributes) - var/trim_left = trim_text(copytext(attribute, 1, findtext(attribute, "="))) - var/trim_right = trim_text(copytext(attribute, findtext(attribute, "=") + 1)) - - if(findtext(trim_right, "list(")) - trim_right = get_list(trim_right, text_strings) - - else if(findtext(trim_right, "~"))//Check for strings - while(findtext(trim_right,"~")) - var/reference_index = copytext(trim_right, findtext(trim_right, "~") + 1) - trim_right = text_strings[text2num(reference_index)] - - //Check for numbers - else if(isnum(text2num(trim_right))) - trim_right = text2num(trim_right) - - //Check for file - else if(copytext(trim_right,1,2) == "'") - trim_right = file(copytext(trim_right, 2, length(trim_right))) - - fields[trim_left] = trim_right - sleep(-1) - - - if(debug_output) - var/return_data = " Debug Fields:" - for(var/item in fields) - return_data += " [item] = [fields[item]];" - debug_file << return_data - - //Begin Instanciation - var/atom/instance - - if(ispath(atom_def,/area)) - instance = locate(atom_def) - if(!istype(instance, atom_def)) - instance = new atom_def - instance.contents.Add(locate(x_coordinate,y_coordinate,z_coordinate)) - - else - instance = new atom_def(locate(x_coordinate,y_coordinate,z_coordinate)) - if(instance) - for(var/item in fields) - instance.vars[item] = fields[item] - else if(!(atom_def in borked_paths)) - borked_paths += atom_def - var/return_data = " Failure [atom_def] @ ([x_coordinate], [y_coordinate], [z_coordinate]) fields:" - for(var/item in fields) - return_data += " [item] = [fields[item]];" - debug_file << return_data + ycrd-- sleep(-1) - return 1 - var/list/borked_paths = list() + //reached End Of File + if(findtext(tfile,quote+"}",zpos,0)+2==tfile_len) + break + sleep(-1) - proc/trim_text(var/what as text) - while(length(what) && findtext(what, " ", 1, 2)) - what = copytext(what, 2) +/** + * Fill a given tile with its area/turf/objects/mobs + * Variable model is one full map line (e.g /turf/unsimulated/wall{icon_state = "rock"},/area/mine/explored) + * + * WORKING : + * + * 1) Read the model string, member by member (delimiter is ',') + * + * 2) Get the path of the atom and store it into a list + * + * 3) a) Check if the member has variables (text within '{' and '}') + * + * 3) b) Construct an associative list with found variables, if any (the atom index in members is the same as its variables in members_attributes) + * + * 4) Instanciates the atom with its variables + * + */ +/dmm_suite/proc/parse_grid(var/model as text,var/xcrd as num,var/ycrd as num,var/zcrd as num) + /*Method parse_grid() + - Accepts a text string containing a comma separated list of type paths of the + same construction as those contained in a .dmm file, and instantiates them. + */ - while(length(what) && findtext(what, " ", length(what))) - what = copytext(what, 1, length(what)) + var/list/members = list()//will contain all members (paths) in model (in our example : /turf/unsimulated/wall and /area/mine/explored) + var/list/members_attributes = list()//will contain lists filled with corresponding variables, if any (in our example : list(icon_state = "rock") and list()) - return what - proc/get_list(var/text, var/list/text_strings) - //First, trim the data to just the list contents - var/list_start = findtext(text, "(") + 1 - var/list_end = findtext(text, ")", list_start) - var/list_contents = copytext(text, list_start, list_end) + ///////////////////////////////////////////////////////// + //Constructing members and corresponding variables lists + //////////////////////////////////////////////////////// - //Then, we seperate it into the individual entries + var/index=1 + var/old_position = 1 + var/dpos - var/list/entries = list() - var/entry_end = 1 + do + //finding next member (e.g /turf/unsimulated/wall{icon_state = "rock"} or /area/mine/explored) + dpos= find_next_delimiter_position(model,old_position,",","{","}")//find next delimiter (comma here) that's not within {...} - for(var/entry_start = 1, entry_end || entry_start != 1, entry_start = entry_end + 1) - entry_end = findtext(list_contents, ",", entry_start) - entries += copytext(list_contents, entry_start, entry_end) + var/full_def = copytext(model,old_position,dpos)//full definition, e.g : /obj/foo/bar{variables=derp} + var/atom_def = text2path(copytext(full_def,1,findtext(full_def,"{")))//path definition, e.g /obj/foo/bar + members.Add(atom_def) + old_position = dpos + 1 - //Finally, we assemble the completed list. - var/list/final_list = list() - for(var/entry in entries) - var/equals_position = findtext(entry, "=") + //transform the variables in text format into a list (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) + var/list/fields = list() - if(equals_position) - var/trim_left = trim_text(copytext(entry, 1, equals_position)) - var/trim_right = trim_text(copytext(entry, equals_position + 1)) + var/variables_start = findtext(full_def,"{") + if(variables_start)//if there's any variable + full_def = copytext(full_def,variables_start+1,length(full_def))//removing the last '}' + fields = text2list(full_def,";") - if(findtext(trim_right, "list(")) - trim_right = get_list(trim_right, text_strings) + //then fill the members_attributes list with the corresponding variables + members_attributes.len++ + members_attributes[index++] = fields - else if(findtext(trim_right, "~"))//Check for strings - while(findtext(trim_right,"~")) - var/reference_index = copytext(trim_right, findtext(trim_right, "~") + 1) - trim_right = text_strings[text2num(reference_index)] + sleep(-1) + while(dpos != 0) - //Check for numbers - else if(isnum(text2num(trim_right))) - trim_right = text2num(trim_right) - //Check for file - else if(copytext(trim_right,1,2) == "'") - trim_right = file(copytext(trim_right, 2, length(trim_right))) + //////////////// + //Instanciation + //////////////// - if(findtext(trim_left, "~"))//Check for strings - while(findtext(trim_left,"~")) - var/reference_index = copytext(trim_left, findtext(trim_left, "~") + 1) - trim_left = text_strings[text2num(reference_index)] + //The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile - final_list[trim_left] = trim_right + //first instance the /area and remove it from the members list + var/length = members.len + var/atom/instance + var/dmm_suite/preloader/_preloader = new(members_attributes[length])//preloader for assigning set variables on atom creation - else - if(findtext(entry, "~"))//Check for strings - while(findtext(entry, "~")) - var/reference_index = copytext(entry, findtext(entry, "~") + 1) - entry = text_strings[text2num(reference_index)] + instance = locate(members[length]) + instance.contents.Add(locate(xcrd,ycrd,zcrd)) - //Check for numbers - else if(isnum(text2num(entry))) - entry = text2num(entry) + if(_preloader && instance) + _preloader.load(instance) - //Check for file - else if(copytext(entry, 1, 2) == "'") - entry = file(copytext(entry, 2, length(entry))) + members.Remove(members[length]) - final_list += entry + //then instance the /turf and remove it from the members list + length = members.len - return final_list \ No newline at end of file + instance_atom(members[length],members_attributes[length],xcrd,ycrd,zcrd) + members.Remove(members[length]) + + //Replace the previous part of the code with this if it's unsafe to assume tiles have ALWAYS an /area AND a /turf + /*while(members.len > 0) + var/length = members.len + var/member = members[length] + + if(ispath(member,/area)) + var/atom/instance + var/dmm_suite/preloader/_preloader = new(members_attributes[length]) + + instance = locate(member) + instance.contents.Add(locate(xcrd,ycrd,zcrd)) + + if(_preloader && instance) + _preloader.load(instance) + + members.Remove(member) + continue + + else if(ispath(member,/turf)) + instance_atom(member,members_attributes[length],xcrd,ycrd,zcrd) + members.Remove(member) + continue + + else + break + */ + + //finally instance all remainings objects/mobs + for(var/k=1,k<=members.len,k++) + instance_atom(members[k],members_attributes[k],xcrd,ycrd,zcrd) + +//////////////// +//Helpers procs +//////////////// + +//Instance an atom at (x,y,z) and gives it the variables in attributes +/dmm_suite/proc/instance_atom(var/path,var/list/attributes, var/x, var/y, var/z) + var/atom/instance + var/dmm_suite/preloader/_preloader = new(attributes) + + instance = new path (locate(x,y,z), _preloader)//first preloader pass + + if(_preloader && instance)//second preloader pass, as some variables may have been reset/changed by New() + _preloader.load(instance) + +//text trimming (both directions) helper proc +//optionally removes quotes before and after the text (for variable name) +/dmm_suite/proc/trim_text(var/what as text,var/trim_quotes=0) + while(length(what) && (findtext(what," ",1,2)))// || findtext(what,quote,1,2))) + what=copytext(what,2,0) + while(length(what) && (findtext(what," ",length(what),0)))// || findtext(what,quote,length(what),0))) + what=copytext(what,1,length(what)) + if(trim_quotes) + while(length(what) && (findtext(what,quote,1,2))) + what=copytext(what,2,0) + while(length(what) && (findtext(what,quote,length(what),0))) + what=copytext(what,1,length(what)) + return what + +//find the position of the next delimiter,skipping whatever is comprised between opening_escape and closing_escape +//returns 0 if reached the last delimiter +/dmm_suite/proc/find_next_delimiter_position(var/text as text,var/initial_position as num, var/delimiter=",",var/opening_escape=quote,var/closing_escape=quote) + var/position = initial_position + var/next_delimiter = findtext(text,delimiter,position,0) + var/next_opening = findtext(text,opening_escape,position,0) + + while((next_opening != 0) && (next_opening < next_delimiter)) + position = findtext(text,closing_escape,next_opening + 1,0)+1 + next_delimiter = findtext(text,delimiter,position,0) + next_opening = findtext(text,opening_escape,position,0) + + return next_delimiter + + +//build a list from variables in text form (e.g {var1="derp"; var2; var3=7} => list(var1="derp", var2, var3=7)) +//return the filled list +/dmm_suite/proc/text2list(var/text as text,var/delimiter=",") + + var/list/to_return = list() + + var/position + var/old_position = 1 + + do + //find next delimiter that is not within "..." + position = find_next_delimiter_position(text,old_position,delimiter) + + //check if this is a simple variable (as in list(var1, var2)) or an associative one (as in list(var1="foo",var2=7)) + var/equal_position = findtext(text,"=",old_position, position) + + var/trim_left = trim_text(copytext(text,old_position,(equal_position ? equal_position : position)),1)//the name of the variable, must trim quotes to build a BYOND compliant associatives list + old_position = position + 1 + + if(equal_position)//associative var, so do the association + var/trim_right = trim_text(copytext(text,equal_position+1,position))//the content of the variable + + //Check for string + if(findtext(trim_right,quote,1,2)) + trim_right = copytext(trim_right,2,findtext(trim_right,quote,3,0)) + + //Check for number + else if(isnum(text2num(trim_right))) + trim_right = text2num(trim_right) + + //Check for file + else if(copytext(trim_right,1,2) == "'") + trim_right = file(copytext(trim_right,2,length(trim_right))) + + //Check for list + else if(copytext(trim_right,1,5) == "list") + trim_right = text2list(copytext(trim_right,6,length(trim_right))) + + to_return[trim_left] = trim_right + + else//simple var + to_return[trim_left] = null + + while(position != 0) + + return to_return + +//atom creation method that preloads variables before creation +atom/New(atom/loc, dmm_suite/preloader/_dmm_preloader) + if(istype(_dmm_preloader, /dmm_suite/preloader)) + _dmm_preloader.load(src) + . = ..() + +////////////////// +//Preloader datum +////////////////// + +/dmm_suite/preloader + parent_type = /datum + var/list/attributes + +/dmm_suite/preloader/New(list/the_attributes) + .=..() + if(!the_attributes.len) + Del() + attributes = the_attributes + +/dmm_suite/preloader/proc/load(atom/what) + for(var/attribute in attributes) + what.vars[attribute] = attributes[attribute] + Del() \ No newline at end of file