diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index 8d87a53e6c7b..7ebdce88b98e 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -96,6 +96,7 @@ var/turf/destturf var/turf/curturf = get_turf(teleatom) + var/area/destarea = get_area(destination) if(precision) var/list/posturfs = circlerangeturfs(destination,precision) destturf = safepick(posturfs) @@ -121,6 +122,7 @@ // Re-Apply lum teleatom.sd_SetLuminosity(prevlum) + destarea.Entered(teleatom) return 1 diff --git a/code/defines/area/Space Station 13 areas.dm b/code/defines/area/Space Station 13 areas.dm index ad162688327b..fb26013588c0 100644 --- a/code/defines/area/Space Station 13 areas.dm +++ b/code/defines/area/Space Station 13 areas.dm @@ -1390,6 +1390,21 @@ proc/process_ghost_teleport_locs() icon_state = "tcomsatlounge" + + + + + + + + + + +/area/turret_protected/AssistantRoom + name = "Assistant Room" + icon_state = "storage" + sd_lighting = 0 + ///////////////////////////////////////////////////////////////////// /* Lists of areas to be used with is_type_in_list. diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm index cdb3f55899c2..9e66a6c23ffe 100644 --- a/code/game/machinery/teleporter.dm +++ b/code/game/machinery/teleporter.dm @@ -56,8 +56,10 @@ for(var/obj/item/device/radio/beacon/R in world) var/turf/T = get_turf(R) - if (!T) continue - if(T.z == 2) continue + if (!T) + continue + if(T.z == 2) + continue var/tmpname = T.loc.name if(areaindex[tmpname]) tmpname = "[tmpname] ([++areaindex[tmpname]])" diff --git a/code/game/machinery/turrets.dm b/code/game/machinery/turrets.dm index c18c056ad7ff..2deb074c0e27 100644 --- a/code/game/machinery/turrets.dm +++ b/code/game/machinery/turrets.dm @@ -11,6 +11,8 @@ //TODO: make teleporting to places trigger Entered() ~Carn +// Done. + /area/turret_protected/Entered(O) ..() if( master && master != src ) @@ -83,6 +85,7 @@ anchored = 1 layer = 3.5 density = 0 + var/obj/machinery/turret/host = null /obj/machinery/turret/proc/isPopping() return (popping!=0) @@ -156,6 +159,7 @@ return if(src.cover==null) src.cover = new /obj/machinery/turretcover(src.loc) + src.cover.host = src protected_area = get_protected_area() if(!enabled || !protected_area || protected_area.turretTargets.len<=0) if(!isDown() && !isPopping()) diff --git a/code/game/master_controller.dm b/code/game/master_controller.dm index 69dc2cb8910f..5426cad9ed3f 100644 --- a/code/game/master_controller.dm +++ b/code/game/master_controller.dm @@ -47,6 +47,8 @@ datum/controller/game_controller world.tick_lag = config.Ticklag + createRandomZlevel() + setup_objects() setupgenetics() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 402b042cfd2e..56ac32b702da 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -234,7 +234,7 @@ verbs += /client/proc/cmd_admin_world_narrate verbs += /client/proc/cmd_debug_del_all verbs += /client/proc/cmd_debug_tog_aliens - verbs += /client/proc/mapload +// verbs += /client/proc/mapload verbs += /client/proc/check_words verbs += /client/proc/drop_bomb verbs += /client/proc/kill_airgroup @@ -325,7 +325,7 @@ verbs -= /client/proc/Cell verbs -= /client/proc/cmd_debug_del_all verbs -= /client/proc/cmd_debug_tog_aliens - verbs -= /client/proc/mapload +// verbs -= /client/proc/mapload verbs -= /client/proc/check_words verbs -= /client/proc/drop_bomb //verbs -= /client/proc/cmd_admin_drop_everything --merged with view variables diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index f3e9634cbcf7..aec0a756455d 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -140,6 +140,10 @@ proc/set_frequency(new_frequency) + if(!radio_controller) + sleep(20) + if(!radio_controller) + return radio_controller.remove_object(src, frequency) frequency = new_frequency radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT) diff --git a/code/modules/maps/SwapMaps.dm b/code/modules/maps/SwapMaps.dm new file mode 100644 index 000000000000..2d3d8e59887c --- /dev/null +++ b/code/modules/maps/SwapMaps.dm @@ -0,0 +1,672 @@ +/* + SwapMaps library by Lummox JR + developed for digitalBYOND + http://www.digitalbyond.org + + Version 2.1 + + The purpose of this library is to make it easy for authors to swap maps + in and out of their game using savefiles. Swapped-out maps can be + transferred between worlds for an MMORPG, sent to the client, etc. + This is facilitated by the use of a special datum and a global list. + + Uses of swapmaps: + + - Temporary battle arenas + - House interiors + - Individual custom player houses + - Virtually unlimited terrain + - Sharing maps between servers running different instances of the same + game + - Loading and saving pieces of maps for reusable room templates + */ + +/* + User Interface: + + VARS: + + swapmaps_iconcache + An associative list of icon files with names, like + 'player.dmi' = "player" + swapmaps_mode + This must be set at runtime, like in world/New(). + + SWAPMAPS_SAV 0 (default) + Uses .sav files for raw /savefile output. + SWAPMAPS_TEXT 1 + Uses .txt files via ExportText() and ImportText(). These maps + are easily editable and appear to take up less space in the + current version of BYOND. + + PROCS: + + SwapMaps_Find(id) + Find a map by its id + SwapMaps_Load(id) + Load a map by its id + SwapMaps_Save(id) + Save a map by its id (calls swapmap.Save()) + SwapMaps_Unload(id) + Save and unload a map by its id (calls swapmap.Unload()) + SwapMaps_Save_All() + Save all maps + SwapMaps_DeleteFile(id) + Delete a map file + SwapMaps_CreateFromTemplate(id) + Create a new map by loading another map to use as a template. + This map has id==src and will not be saved. To make it savable, + change id with swapmap.SetID(newid). + SwapMaps_LoadChunk(id,turf/locorner) + Load a swapmap as a "chunk", at a specific place. A new datum is + created but it's not added to the list of maps to save or unload. + The new datum can be safely deleted without affecting the turfs + it loaded. The purpose of this is to load a map file onto part of + another swapmap or an existing part of the world. + locorner is the corner turf with the lowest x,y,z values. + SwapMaps_SaveChunk(id,turf/corner1,turf/corner2) + Save a piece of the world as a "chunk". A new datum is created + for the chunk, but it can be deleted without destroying any turfs. + The chunk file can be reloaded as a swapmap all its own, or loaded + via SwapMaps_LoadChunk() to become part of another map. + SwapMaps_GetSize(id) + Return a list corresponding to the x,y,z sizes of a map file, + without loading the map. + Returns null if the map is not found. + SwapMaps_AddIconToCache(name,icon) + Cache an icon file by name for space-saving storage + + swapmap.New(id,x,y,z) + Create a new map; specify id, width (x), height (y), and + depth (z) + Default size is world.maxx,world.maxy,1 + swapmap.New(id,turf1,turf2) + Create a new map; specify id and 2 corners + This becomes a /swapmap for one of the compiled-in maps, for + easy saving. + swapmap.New() + Create a new map datum, but does not allocate space or assign an + ID (used for loading). + swapmap.Del() + Deletes a map but does not save + swapmap.Save() + Saves to map_[id].sav + Maps with id==src are not saved. + swapmap.Unload() + Saves the map and then deletes it + Maps with id==src are not saved. + swapmap.SetID(id) + Change the map's id and make changes to the lookup list + swapmap.AllTurfs(z) + Returns a block of turfs encompassing the entire map, or on just + one z-level + z is in world coordinates; it is optional + swapmap.Contains(turf/T) + Returns nonzero if T is inside the map's boundaries. + Also works for objs and mobs, but the proc is not area-safe. + swapmap.InUse() + Returns nonzero if a mob with a key is within the map's + boundaries. + swapmap.LoCorner(z=z1) + Returns locate(x1,y1,z), where z=z1 if none is specified. + swapmap.HiCorner(z=z2) + Returns locate(x2,y2,z), where z=z2 if none is specified. + swapmap.BuildFilledRectangle(turf/corner1,turf/corner2,item) + Builds a filled rectangle of item from one corner turf to the + other, on multiple z-levels if necessary. The corners may be + specified in any order. + item is a type path like /turf/wall or /obj/barrel{full=1}. + swapmap.BuildRectangle(turf/corner1,turf/corner2,item) + Builds an unfilled rectangle of item from one corner turf to + the other, on multiple z-levels if necessary. + swapmap.BuildInTurfs(list/turfs,item) + Builds item on all of the turfs listed. The list need not + contain only turfs, or even only atoms. + */ + +swapmap + var/id // a string identifying this map uniquely + var/x1 // minimum x,y,z coords + var/y1 + var/z1 + var/x2 // maximum x,y,z coords (also used as width,height,depth until positioned) + var/y2 + var/z2 + var/tmp/locked // don't move anyone to this map; it's saving or loading + var/tmp/mode // save as text-mode + var/ischunk // tells the load routine to load to the specified location + + New(_id,x,y,z) + if(isnull(_id)) return + id=_id + mode=swapmaps_mode + if(isturf(x) && isturf(y)) + /* + Special format: Defines a map as an existing set of turfs; + this is useful for saving a compiled map in swapmap format. + Because this is a compiled-in map, its turfs are not deleted + when the datum is deleted. + */ + x1=min(x:x,y:x);x2=max(x:x,y:x) + y1=min(x:y,y:y);y2=max(x:y,y:y) + z1=min(x:z,y:z);z2=max(x:z,y:z) + InitializeSwapMaps() + if(z2>swapmaps_compiled_maxz ||\ + y2>swapmaps_compiled_maxy ||\ + x2>swapmaps_compiled_maxx) + del(src) + return + x2=x?(x):world.maxx + y2=y?(y):world.maxy + z2=z?(z):1 + AllocateSwapMap() + + Del() + // a temporary datum for a chunk can be deleted outright + // for others, some cleanup is necessary + if(!ischunk) + swapmaps_loaded-=src + swapmaps_byname-=id + if(z2>swapmaps_compiled_maxz ||\ + y2>swapmaps_compiled_maxy ||\ + x2>swapmaps_compiled_maxx) + var/list/areas=new + for(var/atom/A in block(locate(x1,y1,z1),locate(x2,y2,z2))) + for(var/obj/O in A) del(O) + for(var/mob/M in A) + if(!M.key) del(M) + else M.loc=null + areas[A.loc]=null + del(A) + // delete areas that belong only to this map + for(var/area/a in areas) + if(a && !a.contents.len) del(a) + if(x2>=world.maxx || y2>=world.maxy || z2>=world.maxz) CutXYZ() + del(areas) + ..() + + /* + Savefile format: + map + id + x // size, not coords + y + z + areas // list of areas, not including default + [each z; 1 to depth] + [each y; 1 to height] + [each x; 1 to width] + type // of turf + AREA // if non-default; saved as a number (index into areas list) + vars // all other changed vars + */ + Write(savefile/S) + var + x;y;z;n + list/areas + area/defarea=locate(world.area) + if(!defarea) defarea=new world.area + areas=list() + for(var/turf/T in block(locate(x1,y1,z1),locate(x2,y2,z2))) + areas[T.loc]=null + for(n in areas) // quickly eliminate associations for smaller storage + areas-=n + areas+=n + areas-=defarea + InitializeSwapMaps() + locked=1 + S["id"] << id + S["z"] << z2-z1+1 + S["y"] << y2-y1+1 + S["x"] << x2-x1+1 + S["areas"] << areas + for(n in 1 to areas.len) areas[areas[n]]=n + var/oldcd=S.cd + for(z=z1,z<=z2,++z) + S.cd="[z-z1+1]" + for(y=y1,y<=y2,++y) + S.cd="[y-y1+1]" + for(x=x1,x<=x2,++x) + S.cd="[x-x1+1]" + var/turf/T=locate(x,y,z) + S["type"] << T.type + if(T.loc!=defarea) S["AREA"] << areas[T.loc] + T.Write(S) + S.cd=".." + S.cd=".." + sleep() + S.cd=oldcd + locked=0 + del(areas) + + Read(savefile/S,_id,turf/locorner) + var + x;y;z;n + list/areas + area/defarea=locate(world.area) + id=_id + if(locorner) + ischunk=1 + x1=locorner.x + y1=locorner.y + z1=locorner.z + if(!defarea) defarea=new world.area + if(!_id) + S["id"] >> id + else + var/dummy + S["id"] >> dummy + S["z"] >> z2 // these are depth, + S["y"] >> y2 // height, + S["x"] >> x2 // width + S["areas"] >> areas + locked=1 + AllocateSwapMap() // adjust x1,y1,z1 - x2,y2,z2 coords + var/oldcd=S.cd + for(z=z1,z<=z2,++z) + S.cd="[z-z1+1]" + for(y=y1,y<=y2,++y) + S.cd="[y-y1+1]" + for(x=x1,x<=x2,++x) + S.cd="[x-x1+1]" + var/tp + S["type"]>>tp + var/turf/T=locate(x,y,z) + T.loc.contents-=T + T=new tp(locate(x,y,z)) + if("AREA" in S.dir) + S["AREA"]>>n + var/area/A=areas[n] + A.contents+=T + else defarea.contents+=T + // clear the turf + for(var/obj/O in T) del(O) + for(var/mob/M in T) + if(!M.key) del(M) + else M.loc=null + // finish the read + T.Read(S) + S.cd=".." + S.cd=".." + sleep() + S.cd=oldcd + locked=0 + del(areas) + + /* + Find an empty block on the world map in which to load this map. + If no space is found, increase world.maxz as necessary. (If the + map is greater in x,y size than the current world, expand + world.maxx and world.maxy too.) + + Ignore certain operations if loading a map as a chunk. Use the + x1,y1,z1 position for it, and *don't* count it as a loaded map. + */ + proc/AllocateSwapMap() + InitializeSwapMaps() + world.maxx=max(x2,world.maxx) // stretch x/y if necessary + world.maxy=max(y2,world.maxy) + if(!ischunk) + if(world.maxz<=swapmaps_compiled_maxz) + z1=swapmaps_compiled_maxz+1 + x1=1;y1=1 + else + var/list/l=ConsiderRegion(1,1,world.maxx,world.maxy,swapmaps_compiled_maxz+1) + x1=l[1] + y1=l[2] + z1=l[3] + del(l) + x2+=x1-1 + y2+=y1-1 + z2+=z1-1 + world.maxz=max(z2,world.maxz) // stretch z if necessary + if(!ischunk) + swapmaps_loaded[src]=null + swapmaps_byname[id]=src + + proc/ConsiderRegion(X1,Y1,X2,Y2,Z1,Z2) + while(1) + var/nextz=0 + var/swapmap/M + for(M in swapmaps_loaded) + if(M.z2Z2) || M.z1>=Z1+z2 ||\ + M.x1>X2 || M.x2=X1+x2 ||\ + M.y1>Y2 || M.y2=Y1+y2) continue + // look for sub-regions with a defined ceiling + var/nz2=Z2?(Z2):Z1+z2-1+M.z2-M.z1 + if(M.x1>=X1+x2) + .=ConsiderRegion(X1,Y1,M.x1-1,Y2,Z1,nz2) + if(.) return + else if(M.x2<=X2-x2) + .=ConsiderRegion(M.x2+1,Y1,X2,Y2,Z1,nz2) + if(.) return + if(M.y1>=Y1+y2) + .=ConsiderRegion(X1,Y1,X2,M.y1-1,Z1,nz2) + if(.) return + else if(M.y2<=Y2-y2) + .=ConsiderRegion(X1,M.y2+1,X2,Y2,Z1,nz2) + if(.) return + nextz=nextz?min(nextz,M.z2+1):(M.z2+1) + if(!M) + /* If nextz is not 0, then at some point there was an overlap that + could not be resolved by using an area to the side */ + if(nextz) Z1=nextz + if(!nextz || (Z2 && Z2-Z1+1=z2)?list(X1,Y1,Z1):null + X1=1;X2=world.maxx + Y1=1;Y2=world.maxy + + proc/CutXYZ() + var/mx=swapmaps_compiled_maxx + var/my=swapmaps_compiled_maxy + var/mz=swapmaps_compiled_maxz + for(var/swapmap/M in swapmaps_loaded) // may not include src + mx=max(mx,M.x2) + my=max(my,M.y2) + mz=max(mz,M.z2) + world.maxx=mx + world.maxy=my + world.maxz=mz + + // save and delete + proc/Unload() + Save() + del(src) + + proc/Save() + if(id==src) return 0 + var/savefile/S=mode?(new):new("map_[id].sav") + S << src + while(locked) sleep(1) + if(mode) + fdel("map_[id].txt") + S.ExportText("/","map_[id].txt") + return 1 + + // this will not delete existing savefiles for this map + proc/SetID(newid) + swapmaps_byname-=id + id=newid + swapmaps_byname[id]=src + + proc/AllTurfs(z) + if(isnum(z) && (zz2)) return null + return block(LoCorner(z),HiCorner(z)) + + // this could be safely called for an obj or mob as well, but + // probably not an area + proc/Contains(turf/T) + return (T && T.x>=x1 && T.x<=x2\ + && T.y>=y1 && T.y<=y2\ + && T.z>=z1 && T.z<=z2) + + proc/InUse() + for(var/turf/T in AllTurfs()) + for(var/mob/M in T) if(M.key) return 1 + + proc/LoCorner(z=z1) + return locate(x1,y1,z) + proc/HiCorner(z=z2) + return locate(x2,y2,z) + + /* + Build procs: Take 2 turfs as corners, plus an item type. + An item may be like: + + /turf/wall + /obj/fence{icon_state="iron"} + */ + proc/BuildFilledRectangle(turf/T1,turf/T2,item) + if(!Contains(T1) || !Contains(T2)) return + var/turf/T=T1 + // pick new corners in a block()-friendly form + T1=locate(min(T1.x,T2.x),min(T1.y,T2.y),min(T1.z,T2.z)) + T2=locate(max(T.x,T2.x),max(T.y,T2.y),max(T.z,T2.z)) + for(T in block(T1,T2)) new item(T) + + proc/BuildRectangle(turf/T1,turf/T2,item) + if(!Contains(T1) || !Contains(T2)) return + var/turf/T=T1 + // pick new corners in a block()-friendly form + T1=locate(min(T1.x,T2.x),min(T1.y,T2.y),min(T1.z,T2.z)) + T2=locate(max(T.x,T2.x),max(T.y,T2.y),max(T.z,T2.z)) + if(T2.x-T1.x<2 || T2.y-T1.y<2) BuildFilledRectangle(T1,T2,item) + else + //for(T in block(T1,T2)-block(locate(T1.x+1,T1.y+1,T1.z),locate(T2.x-1,T2.y-1,T2.z))) + for(T in block(T1,locate(T2.x,T1.y,T2.z))) new item(T) + for(T in block(locate(T1.x,T2.y,T1.z),T2)) new item(T) + for(T in block(locate(T1.x,T1.y+1,T1.z),locate(T1.x,T2.y-1,T2.z))) new item(T) + for(T in block(locate(T2.x,T1.y+1,T1.z),locate(T2.x,T2.y-1,T2.z))) new item(T) + + /* + Supplementary build proc: Takes a list of turfs, plus an item + type. Actually the list doesn't have to be just turfs. + */ + proc/BuildInTurfs(list/turfs,item) + for(var/T in turfs) new item(T) + +atom + Write(savefile/S) + for(var/V in vars-"x"-"y"-"z"-"contents"-"icon"-"overlays"-"underlays") + if(issaved(vars[V])) + if(vars[V]!=initial(vars[V])) S[V]<>ic + if(istext(ic)) icon=swapmaps_iconcache[ic] + if(l && contents!=l) + contents+=l + del(l) + + +// set this up (at runtime) as follows: +// list(\ +// 'player.dmi'="player",\ +// 'monster.dmi'="monster",\ +// ... +// 'item.dmi'="item") +var/list/swapmaps_iconcache + +// preferred mode; sav or text +var/const/SWAPMAPS_SAV=0 +var/const/SWAPMAPS_TEXT=1 +var/swapmaps_mode=SWAPMAPS_SAV + +var/swapmaps_compiled_maxx +var/swapmaps_compiled_maxy +var/swapmaps_compiled_maxz +var/swapmaps_initialized +var/swapmaps_loaded +var/swapmaps_byname + +proc/InitializeSwapMaps() + if(swapmaps_initialized) return + swapmaps_initialized=1 + swapmaps_compiled_maxx=world.maxx + swapmaps_compiled_maxy=world.maxy + swapmaps_compiled_maxz=world.maxz + swapmaps_loaded=list() + swapmaps_byname=list() + if(swapmaps_iconcache) + for(var/V in swapmaps_iconcache) + // reverse-associate everything + // so you can look up an icon file by name or vice-versa + swapmaps_iconcache[swapmaps_iconcache[V]]=V + +proc/SwapMaps_AddIconToCache(name,icon) + if(!swapmaps_iconcache) swapmaps_iconcache=list() + swapmaps_iconcache[name]=icon + swapmaps_iconcache[icon]=name + +proc/SwapMaps_Find(id) + InitializeSwapMaps() + return swapmaps_byname[id] + +proc/SwapMaps_Load(id) + InitializeSwapMaps() + var/swapmap/M=swapmaps_byname[id] + if(!M) + var/savefile/S + var/text=0 + if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[id].txt")) + text=1 + else if(fexists("map_[id].sav")) + S=new("map_[id].sav") + else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[id].txt")) + text=1 + else return // no file found + if(text) + S=new + S.ImportText("/",file("map_[id].txt")) + S >> M + while(M.locked) sleep(1) + M.mode=text + return M + +proc/SwapMaps_Save(id) + InitializeSwapMaps() + var/swapmap/M=swapmaps_byname[id] + if(M) M.Save() + return M + +proc/SwapMaps_Save_All() + InitializeSwapMaps() + for(var/swapmap/M in swapmaps_loaded) + if(M) M.Save() + +proc/SwapMaps_Unload(id) + InitializeSwapMaps() + var/swapmap/M=swapmaps_byname[id] + if(!M) return // return silently from an error + M.Unload() + return 1 + +proc/SwapMaps_DeleteFile(id) + fdel("map_[id].sav") + fdel("map_[id].txt") + +proc/SwapMaps_CreateFromTemplate(template_id) + var/swapmap/M=new + var/savefile/S + var/text=0 + if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[template_id].txt")) + text=1 + else if(fexists("map_[template_id].sav")) + S=new("map_[template_id].sav") + else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[template_id].txt")) + text=1 + else + world.log << "SwapMaps error in SwapMaps_CreateFromTemplate(): map_[template_id] file not found." + return + if(text) + S=new + S.ImportText("/",file("map_[template_id].txt")) + /* + This hacky workaround is needed because S >> M will create a brand new + M to fill with data. There's no way to control the Read() process + properly otherwise. The //.0 path should always match the map, however. + */ + S.cd="//.0" + M.Read(S,M) + M.mode=text + while(M.locked) sleep(1) + return M + +proc/SwapMaps_LoadChunk(chunk_id,turf/locorner) + var/swapmap/M=new + var/savefile/S + var/text=0 + if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[chunk_id].txt")) + text=1 + else if(fexists("map_[chunk_id].sav")) + S=new("map_[chunk_id].sav") + else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[chunk_id].txt")) + text=1 + else + world.log << "SwapMaps error in SwapMaps_LoadChunk(): map_[chunk_id] file not found." + return + if(text) + S=new + S.ImportText("/",file("map_[chunk_id].txt")) + /* + This hacky workaround is needed because S >> M will create a brand new + M to fill with data. There's no way to control the Read() process + properly otherwise. The //.0 path should always match the map, however. + */ + S.cd="//.0" + M.Read(S,M,locorner) + while(M.locked) sleep(1) + del(M) + return 1 + +proc/SwapMaps_SaveChunk(chunk_id,turf/corner1,turf/corner2) + if(!corner1 || !corner2) + world.log << "SwapMaps error in SwapMaps_SaveChunk():" + if(!corner1) world.log << " corner1 turf is null" + if(!corner2) world.log << " corner2 turf is null" + return + var/swapmap/M=new + M.id=chunk_id + M.ischunk=1 // this is a chunk + M.x1=min(corner1.x,corner2.x) + M.y1=min(corner1.y,corner2.y) + M.z1=min(corner1.z,corner2.z) + M.x2=max(corner1.x,corner2.x) + M.y2=max(corner1.y,corner2.y) + M.z2=max(corner1.z,corner2.z) + M.mode=swapmaps_mode + M.Save() + while(M.locked) sleep(1) + del(M) + return 1 + +proc/SwapMaps_GetSize(id) + var/savefile/S + var/text=0 + if(swapmaps_mode==SWAPMAPS_TEXT && fexists("map_[id].txt")) + text=1 + else if(fexists("map_[id].sav")) + S=new("map_[id].sav") + else if(swapmaps_mode!=SWAPMAPS_TEXT && fexists("map_[id].txt")) + text=1 + else + world.log << "SwapMaps error in SwapMaps_GetSize(): map_[id] file not found." + return + if(text) + S=new + S.ImportText("/",file("map_[id].txt")) + /* + The //.0 path should always be the map. There's no other way to + read this data. + */ + S.cd="//.0" + var/x + var/y + var/z + S["x"] >> x + S["y"] >> y + S["z"] >> z + return list(x,y,z) diff --git a/code/modules/maps/dmm_suite.dm b/code/modules/maps/dmm_suite.dm new file mode 100644 index 000000000000..aeda5e63ccfd --- /dev/null +++ b/code/modules/maps/dmm_suite.dm @@ -0,0 +1,73 @@ +var/global/dmm_suite/maploader = new + +dmm_suite{ + /* + + dmm_suite version 1.0 + Released January 30th, 2011. + + defines the object /dmm_suite + - Provides the proc load_map() + - Loads the specified map file onto the specified z-level. + - provides the proc write_map() + - Returns a text string of the map in dmm format + ready for output to a file. + - provides the proc save_map() + - Returns a .dmm file if map is saved + - Returns FALSE if map fails to save + + The dmm_suite provides saving and loading of map files in BYOND's native DMM map + format. It approximates the map saving and loading processes of the Dream Maker + and Dream Seeker programs so as to allow editing, saving, and loading of maps at + runtime. + + ------------------------ + + To save a map at runtime, create an instance of /dmm_suite, and then call + write_map(), which accepts three arguments: + - A turf representing one corner of a three dimensional grid (Required). + - Another turf representing the other corner of the same grid (Required). + - Any, or a combination, of several bit flags (Optional, see documentation). + + The order in which the turfs are supplied does not matter, the /dmm_writer will + determine the grid containing both, in much the same way as DM's block() function. + write_map() will then return a string representing the saved map in dmm format; + this string can then be saved to a file, or used for any other purose. + + ------------------------ + + To load a map at runtime, create an instance of /dmm_suite, and then call load_map(), + which accepts two arguments: + - A .dmm file to load (Required). + - A number representing the z-level on which to start loading the map (Optional). + + The /dmm_suite will load the map file starting on the specified z-level. If no + z-level was specified, world.maxz will be increased so as to fit the map. Note + that if you wish to load a map onto a z-level that already has objects on it, + you will have to handle the removal of those objects. Otherwise the new map will + simply load the new objects on top of the old ones. + + Also note that all type paths specified in the .dmm file must exist in the world's + code, and that the /dmm_reader trusts that files to be loaded are in fact valid + .dmm files. Errors in the .dmm format will cause runtime errors. + + */ + + verb/load_map(var/dmm_file as file, var/z_offset as num){ + // dmm_file: A .dmm file to load (Required). + // z_offset: A number representing the z-level on which to start loading the map (Optional). + } + verb/write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num){ + // t1: A turf representing one corner of a three dimensional grid (Required). + // t2: Another turf representing the other corner of the same grid (Required). + // flags: Any, or a combination, of several bit flags (Optional, see documentation). + } + + // save_map is included as a legacy proc. Use write_map instead. + verb/save_map(var/turf/t1 as turf, var/turf/t2 as turf, var/map_name as text, var/flags as num){ + // t1: A turf representing one corner of a three dimensional grid (Required). + // t2: Another turf representing the other corner of the same grid (Required). + // map_name: A valid name for the map to be saved, such as "castle" (Required). + // flags: Any, or a combination, of several bit flags (Optional, see documentation). + } + } \ No newline at end of file diff --git a/code/modules/maps/fromdmp.dm b/code/modules/maps/fromdmp.dm new file mode 100644 index 000000000000..c9074f041740 --- /dev/null +++ b/code/modules/maps/fromdmp.dm @@ -0,0 +1,237 @@ +/* + DMP to swapmap converter + version 1.0 + + by Lummox JR + */ + +mob/verb/Convert(filename as file) + dmp2swapmap(filename) + +proc/d2sm_prepmap(filename) + var/txt = file2text(filename) + if(!txt) return + var/i,j + i=findText(txt,ascii2text(13)) // eliminate carriage returns + while(i) + txt=copytext(txt,1,i)+copytext(txt,i+1) + i=findText(txt,ascii2text(13),i) + i=findText(txt,"\\\n") + while(i) + for(j=i+2,j<=length(txt),++j) if(text2ascii(txt,j)>32) break + txt=copytext(txt,1,i)+copytext(txt,j) + i=findText(txt,"\\\n",i) + return txt + +proc/dmp2swapmap(filename) + //var/txt = file2text(filename) + //if(!txt) return + var/txt = d2sm_prepmap(filename) + var/mapname="[filename]" + var/i,j,k + i=findtext(mapname,".dmp") + while(i && i+432) break + txt=copytext(txt,1,i)+copytext(txt,j) + i=findText(txt,"\\\n",i) */ + var/list/codes=new + var/codelen=1 + var/list/areas + var/mode=34 + var/z=0 + var/X=0,Y=0,Z=0 + while(txt) + if(text2ascii(txt)==34) + if(mode!=34) + world << "Corrupt map file [filename]: Unexpected code found after z-level [z]" + return + // standard line: + // "a" = (/obj, /obj, /turf, /area) + i=findtext(txt,"\"",2) + var/code=copytext(txt,2,i) + codelen=length(code) + i=findtext(txt,"(",i) + if(!i) + world << "Corrupt map file [filename]: No type list follows \"[code]\"" + return + k=findtext(txt,"\n",++i) + j=(k || length(txt+1)) + while(--j>=i && text2ascii(txt,j)!=41) + if(j2) codetrans+=d2sm_Contents(L,L.len-2,"\t\t\t\t") + codes[code]=copytext(codetrans,1,length(codetrans)) + else if(text2ascii(txt)==40) + mode=40 + // standard line (top-down, left-right symbol order): + // (1,1,1) = {" + // abcde + // bcdef + // "} + i=d2sm_MatchBrace(txt,1,40) + if(!i) + world << "Corrupt map file [filename]: No matching ) for coordinates: [copytext(txt,1,findtext(txt,"\n"))]" + return + var/list/coords=d2sm_ParseCommaList(copytext(txt,2,i)) + if(istext(coords) || coords.len!=3) + world << "Corrupt map file [filename]: [istext(coords)?(coords):"[copytext(txt,1,i+1)] is not a valid (x,y,z) coordinate"]" + return + j=findtext(txt,"{",i+1) + if(!j) + world << "Corrupt map file [filename]: No braces {} following [copytext(txt,1,i+1)]" + return + k=d2sm_MatchBrace(txt,j,123) + if(!k) + world << "Corrupt map file [filename]: No closing brace } following [copytext(txt,1,i+1)]" + return + var/mtxt=copytext(txt,j+1,k) + if(findText(mtxt,"\"\n")!=1 || !findText(mtxt,"\n\"",length(mtxt)-1)) + world << findText(mtxt,"\"\n") + world << findText(mtxt,"\n\"",length(mtxt)-1) + world << "Corrupt map file [filename]: No quotes in braces following [copytext(txt,1,i+1)]" + return + mtxt=copytext(mtxt,2,length(mtxt)) + var/_x=0,_y=0 + for(i=1,,++_y) + j=findText(mtxt,"\n",i+1) + if(!j) break + _x=max(_x,(j-i-1)/codelen) + i=j + X=max(X,_x) + Y=max(Y,_y) + z=text2num(coords[3]) + Z=max(Z,z) + txt=copytext(txt,k+1) + else + i=findtext(txt,"\n") + txt=i?copytext(txt,i+1):null + world << "Map size: [X],[Y],[Z]" + //for(var/code in codes) + // world << "Code \"[code]\":\n[codes[code]]" + fdel("map_[mapname].txt") + var/F = file("map_[mapname].txt") + F << ". = object(\".0\")\n.0\n\ttype = /swapmap\n\tid = \"[mapname]\"\n\tz = [Z]\n\ty = [Y]\n\tx = [X]" + if(areas) + txt="" + for(i=0,i0,--y) // map is top-down + ++i + F << "\t\t[y]" + for(var/x in 1 to _x) + F << "\t\t\t[x]" + j=i+codelen + F << codes[copytext(mtxt,i,j)] + i=j + txt=copytext(txt,k+1) + /* for(z in 1 to Z) + F << "\t[z]" + for(var/y in 1 to Y) + F << "\t\t[y]" + for(var/x in 1 to X) + F << "\t\t\t[x]" + F << codes[pick(codes)] */ + +proc/d2sm_ParseCommaList(txt) + var/list/L=new + var/i,ch + for(i=1,i<=length(txt),++i) + if(text2ascii(txt,i)>32) break + for(,i<=length(txt),++i) + ch=text2ascii(txt,i) + if(ch==44) + L+=copytext(txt,1,i) + for(++i,i<=length(txt),++i) if(text2ascii(txt,i)>32) break + txt=copytext(txt,i) + i=0;continue + if(ch==40 || ch==91 || ch==123) + i=d2sm_MatchBrace(txt,i,ch) + if(!i) return "No matching brace found for [ascii2text(ch)]" + if(i>1) L+=copytext(txt,1,i) + return L + +proc/d2sm_MatchBrace(txt, i, which) + if(which==40) ++which + else which+=2 + var/j,ch + for(j=i+1,j<=length(txt),++j) + ch=text2ascii(txt,j) + if(ch==which) return j + if(ch==40 || ch==91 || ch==123) + j=d2sm_MatchBrace(txt,j,ch) + if(!j) return 0 + +proc/d2sm_ConvertType(tt,tabs="") + var/i=findText(tt,"{") + if(!i) return "[tabs]type = [tt]\n" + .="[tabs]type = [copytext(tt,1,i)]\n" + var/list/L=d2sm_ParseCommaList(copytext(tt,i+1,d2sm_MatchBrace(tt,i,123))) + if(istext(L)) return + for(var/pair in L) + .="[.][tabs][pair]\n" + +proc/d2sm_Contents(list/conts,n,tabs="") + .="[tabs]contents = list(" + var/i + for(i=0,ilength(zgrid)){break} + sleep(-1) + } + if(findtext(tfile,quote+"}",zpos,0)+2==tfile_len){break} + sleep(-1) + } + } + 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. + */ + var/list/text_strings[0] + 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. + */ + text_strings.len=index + text_strings[index] = copytext(model,findtext(model,quote)+1,findtext(model,quote,findtext(model,quote)+1,0)) + model = copytext(model,1,findtext(model,quote))+"~[index]"+copytext(model,findtext(model,quote,findtext(model,quote)+1,0)+1,0) + sleep(-1) + } + var/list/old_turf_underlays[0] + var/old_turf_density + var/old_turf_opacity + /*The old_turf variables store information about turfs instantiated in this location/iteration. + This is done to approximate the layered turf effect of DM's map editor. + An image of each turf is stored in old_turf_underlays[], and is later added to the new turf's underlays. + */ + for(var/dpos=1;dpos!=0;dpos=findtext(model,",",dpos,0)+1){ + /*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. + */ + var/full_def = copytext(model,dpos,findtext(model,",",dpos,0)) + var/atom_def = text2path(copytext(full_def,1,findtext(full_def,"{"))) + var/list/attributes[0] + if(findtext(full_def,"{")){ + full_def = copytext(full_def,1,length(full_def)) + for(var/apos=findtext(full_def,"{")+1;apos!=0;apos=findtext(full_def,";",apos,0)+1){ + //Loop: Identifies each attribute/value pair, and stores it in attributes[]. + attributes.Add(copytext(full_def,apos,findtext(full_def,";",apos,0))) + if(!findtext(copytext(full_def,apos,0),";")){break} + sleep(-1) + } + } + //Construct attributes associative list + var/list/fields = new(0) + for(var/index=1;index<=attributes.len;index++){ + var/trim_left = trim_text(copytext(attributes[index],1,findtext(attributes[index],"="))) + var/trim_right = trim_text(copytext(attributes[index],findtext(attributes[index],"=")+1,0)) + //Check for string + if(findtext(trim_right,"~")){ + var/reference_index = copytext(trim_right,findtext(trim_right,"~")+1,0) + trim_right=text_strings[text2num(reference_index)] + } + //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))) + } + fields[trim_left] = trim_right + } + //End construction + + + //Begin Instanciation + var/atom/instance + var/dmm_suite/preloader/_preloader = new(fields) + if(ispath(atom_def,/area)){ + instance = locate(atom_def) + instance.contents.Add(locate(xcrd,ycrd,zcrd)) + } + else if(ispath(atom_def,/turf)){ + var/turf/old_turf = locate(xcrd,ycrd,zcrd) + if(old_turf.density){old_turf_density = 1} + if(old_turf.opacity){old_turf_opacity = 1} + if(old_turf.icon){ + var/image/old_turf_image = image(old_turf.icon,null,old_turf.icon_state,old_turf.layer,old_turf.dir) + old_turf_underlays.Add(old_turf_image) + } + instance = new atom_def(old_turf, _preloader) + for(var/inverse_index=old_turf_underlays.len;inverse_index;inverse_index--){ + var/image/image_underlay = old_turf_underlays[inverse_index] + image_underlay.loc = instance + instance.underlays.Add(image_underlay) + } + if(!instance.density){instance.density = old_turf_density} + if(!instance.opacity){instance.opacity = old_turf_opacity} + } + + if(_preloader && instance){ + _preloader.load(instance) + } + //End Instanciation + if(!findtext(copytext(model,dpos,0),",")){break} + sleep(-1) + } +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + for(var/dpos=1;dpos!=0;dpos=findtext(model,",",dpos,0)+1) + { + /*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. + */ + var/full_def = copytext(model,dpos,findtext(model,",",dpos,0)) + var/atom_def = text2path(copytext(full_def,1,findtext(full_def,"{"))) + var/list/attributes[0] + if(findtext(full_def,"{")){ + full_def = copytext(full_def,1,length(full_def)) + for(var/apos=findtext(full_def,"{")+1;apos!=0;apos=findtext(full_def,";",apos,0)+1){ + //Loop: Identifies each attribute/value pair, and stores it in attributes[]. + attributes.Add(copytext(full_def,apos,findtext(full_def,";",apos,0))) + if(!findtext(copytext(full_def,apos,0),";")){break} + sleep(-1) + } + } + //Construct attributes associative list + var/list/fields = new(0) + for(var/index=1;index<=attributes.len;index++){ + var/trim_left = trim_text(copytext(attributes[index],1,findtext(attributes[index],"="))) + var/trim_right = trim_text(copytext(attributes[index],findtext(attributes[index],"=")+1,0)) + //Check for string + if(findtext(trim_right,"~")){ + var/reference_index = copytext(trim_right,findtext(trim_right,"~")+1,0) + trim_right=text_strings[text2num(reference_index)] + } + //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))) + } + fields[trim_left] = trim_right + } + //End construction + + + //Begin Instanciation + var/atom/instance + var/dmm_suite/preloader/_preloader = new(fields) + if(!ispath(atom_def,/area) && !ispath(atom_def,/turf)) + { + instance = new atom_def(locate(xcrd,ycrd,zcrd), _preloader) + } + + + if(_preloader && instance) + { + _preloader.load(instance) + } + //End Instanciation + if(!findtext(copytext(model,dpos,0),",")){break} + sleep(-1) + } + + + + + } + trim_text(var/what as text){ + while(length(what) && findtext(what," ",1,2)){ + what=copytext(what,2,0) + } + while(length(what) && findtext(what," ",length(what),0)){ + what=copytext(what,1,length(what)) + } + return what + } + } + } +atom/New(atom/loc, dmm_suite/preloader/_dmm_preloader){ + if(istype(_dmm_preloader, /dmm_suite/preloader)){ + _dmm_preloader.load(src) + } + . = ..() + } +dmm_suite{ + preloader{ + parent_type = /datum + var{ + list/attributes + } + New(list/the_attributes){ + .=..() + if(!the_attributes.len){ Del()} + attributes = the_attributes + } + proc{ + load(atom/what){ + for(var/attribute in attributes){ + what.vars[attribute] = attributes[attribute] + } + Del() + } + } + } + } + diff --git a/code/modules/maps/writer.dm b/code/modules/maps/writer.dm new file mode 100644 index 000000000000..e405b85a98f0 --- /dev/null +++ b/code/modules/maps/writer.dm @@ -0,0 +1,174 @@ +#define DMM_IGNORE_AREAS 1 +#define DMM_IGNORE_TURFS 2 +#define DMM_IGNORE_OBJS 4 +#define DMM_IGNORE_NPCS 8 +#define DMM_IGNORE_PLAYERS 16 +#define DMM_IGNORE_MOBS 24 +dmm_suite{ + var{ + quote = "\"" + list/letter_digits = list( + "a","b","c","d","e", + "f","g","h","i","j", + "k","l","m","n","o", + "p","q","r","s","t", + "u","v","w","x","y", + "z", + "A","B","C","D","E", + "F","G","H","I","J", + "K","L","M","N","O", + "P","Q","R","S","T", + "U","V","W","X","Y", + "Z" + ) + } + save_map(var/turf/t1 as turf, var/turf/t2 as turf, var/map_name as text, var/flags as num){ + //Check for illegal characters in file name... in a cheap way. + if(!((ckeyEx(map_name)==map_name) && ckeyEx(map_name))){ + CRASH("Invalid text supplied to proc save_map, invalid characters or empty string.") + } + //Check for valid turfs. + if(!isturf(t1) || !isturf(t2)){ + CRASH("Invalid arguments supplied to proc save_map, arguments were not turfs.") + } + var/file_text = write_map(t1,t2,flags) + if(fexists("[map_name].dmm")){ + fdel("[map_name].dmm") + } + var/saved_map = file("[map_name].dmm") + saved_map << file_text + return saved_map + } + write_map(var/turf/t1 as turf, var/turf/t2 as turf, var/flags as num){ + //Check for valid turfs. + if(!isturf(t1) || !isturf(t2)){ + CRASH("Invalid arguments supplied to proc write_map, arguments were not turfs.") + } + var/turf/nw = locate(min(t1.x,t2.x),max(t1.y,t2.y),min(t1.z,t2.z)) + var/turf/se = locate(max(t1.x,t2.x),min(t1.y,t2.y),max(t1.z,t2.z)) + var/list/templates[0] + var/template_buffer = {""} + var/dmm_text = {""} + for(var/pos_z=nw.z;pos_z<=se.z;pos_z++){ + for(var/pos_y=nw.y;pos_y>=se.y;pos_y--){ + for(var/pos_x=nw.x;pos_x<=se.x;pos_x++){ + var/turf/test_turf = locate(pos_x,pos_y,pos_z) + var/test_template = make_template(test_turf, flags) + var/template_number = templates.Find(test_template) + if(!template_number){ + templates.Add(test_template) + template_number = templates.len + } + template_buffer += "[template_number]," + } + template_buffer += ";" + } + template_buffer += "." + } + var/key_length = round/*floor*/(log(letter_digits.len,templates.len-1)+1) + var/list/keys[templates.len] + for(var/key_pos=1;key_pos<=templates.len;key_pos++){ + keys[key_pos] = get_model_key(key_pos,key_length) + dmm_text += {""[keys[key_pos]]" = ([templates[key_pos]])\n"} + } + var/z_level = 0 + for(var/z_pos=1;TRUE;z_pos=findtext(template_buffer,".",z_pos)+1){ + if(z_pos>=length(template_buffer)){break} + if(z_level){dmm_text+={"\n"}} + dmm_text += {"\n(1,1,[++z_level]) = {"\n"} + var/z_block = copytext(template_buffer,z_pos,findtext(template_buffer,".",z_pos)) + for(var/y_pos=1;TRUE;y_pos=findtext(z_block,";",y_pos)+1){ + if(y_pos>=length(z_block)){break} + var/y_block = copytext(z_block,y_pos,findtext(z_block,";",y_pos)) + for(var/x_pos=1;TRUE;x_pos=findtext(y_block,",",x_pos)+1){ + if(x_pos>=length(y_block)){break} + var/x_block = copytext(y_block,x_pos,findtext(y_block,",",x_pos)) + var/key_number = text2num(x_block) + var/temp_key = keys[key_number] + dmm_text += temp_key + sleep(-1) + } + dmm_text += {"\n"} + sleep(-1) + } + dmm_text += {"\"}"} + sleep(-1) + } + return dmm_text + } + proc{ + make_template(var/turf/model as turf, var/flags as num){ + var/template = "" + var/obj_template = "" + var/mob_template = "" + var/turf_template = "" + if(!(flags & DMM_IGNORE_TURFS)){ + turf_template = "[model.type][check_attributes(model)]," + } else{ turf_template = "[world.turf],"} + var/area_template = "" + if(!(flags & DMM_IGNORE_OBJS)){ + for(var/obj/O in model.contents){ + obj_template += "[O.type][check_attributes(O)]," + } + } + for(var/mob/M in model.contents){ + if(M.client){ + if(!(flags & DMM_IGNORE_PLAYERS)){ + mob_template += "[M.type][check_attributes(M)]," + } + } + else{ + if(!(flags & DMM_IGNORE_NPCS)){ + mob_template += "[M.type][check_attributes(M)]," + } + } + } + if(!(flags & DMM_IGNORE_AREAS)){ + var/area/m_area = model.loc + area_template = "[m_area.type][check_attributes(m_area)]" + } else{ area_template = "[world.area]"} + template = "[obj_template][mob_template][turf_template][area_template]" + return template + } + check_attributes(var/atom/A){ + var/attributes_text = {"{"} + for(var/V in A.vars){ + sleep(-1) + if((!issaved(A.vars[V])) || (A.vars[V]==initial(A.vars[V]))){continue} + if(istext(A.vars[V])){ + attributes_text += {"[V] = "[A.vars[V]]""} + } + else if(isnum(A.vars[V])||ispath(A.vars[V])){ + attributes_text += {"[V] = [A.vars[V]]"} + } + else if(isicon(A.vars[V])||isfile(A.vars[V])){ + attributes_text += {"[V] = '[A.vars[V]]'"} + } + else{ + continue + } + if(attributes_text != {"{"}){ + attributes_text+={"; "} + } + } + if(attributes_text=={"{"}){ + return + } + if(copytext(attributes_text, length(attributes_text)-1, 0) == {"; "}){ + attributes_text = copytext(attributes_text, 1, length(attributes_text)-1) + } + attributes_text += {"}"} + return attributes_text + } + get_model_key(var/which as num, var/key_length as num){ + var/key = "" + var/working_digit = which-1 + for(var/digit_pos=key_length;digit_pos>=1;digit_pos--){ + var/place_value = round/*floor*/(working_digit/(letter_digits.len**(digit_pos-1))) + working_digit-=place_value*(letter_digits.len**(digit_pos-1)) + key = "[key][letter_digits[place_value+1]]" + } + return key + } + } + } \ No newline at end of file diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 5a63176c0870..9380005c8467 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -157,6 +157,9 @@ SetParalysis(0) SetStunned(0) SetWeakened(0) + src.radiation = 0 + src.nutrition = 400 + src.bodytemperature = 310 //src.health = 100 if(ishuman(src)) var/mob/living/carbon/human/H = src diff --git a/maps/RandomZLevels/assistantChamber.dmm b/maps/RandomZLevels/assistantChamber.dmm new file mode 100644 index 000000000000..f0cc82d0bd18 --- /dev/null +++ b/maps/RandomZLevels/assistantChamber.dmm @@ -0,0 +1,66 @@ +"a" = (/turf/space,/area) +"b" = (/turf/unsimulated/wall,/area) +"c" = (/obj/machinery/shield,/turf/simulated/wall/cult,/area/turret_protected/AssistantRoom) +"d" = (/obj/machinery/turret{dir = 8; lasers = 1; name = "Angry Turret"},/obj/machinery/light{dir = 1},/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"e" = (/obj/machinery/light{icon_state = "tube1"; dir = 8},/obj/machinery/turret{dir = 8; lasers = 1; name = "Angry Turret"},/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"f" = (/obj/item/weapon/gift,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"g" = (/obj/effect/accelerated_particle,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"h" = (/obj/item/weapon/gun/energy/gun/nuclear,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"i" = (/obj/item/device/soulstone,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"j" = (/obj/item/ammo_magazine,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"k" = (/obj/effect/rune,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"l" = (/obj/machinery/turret{dir = 8; lasers = 1; name = "Angry Turret"},/obj/machinery/light{icon_state = "tube1"; dir = 4},/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"m" = (/obj/item/weapon/mousetrap/armed,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"n" = (/obj/item/weapon/nullrod,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"o" = (/obj/item/weapon/card/id/captains_spare,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"p" = (/obj/item/weapon/gun/energy/pulse_rifle/M1911,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"q" = (/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"r" = (/obj/item/weapon/card/emag,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"s" = (/obj/item/tk_grab,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"t" = (/obj/item/device/powersink,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"u" = (/obj/item/device/radio/beacon,/obj/item/weapon/caution,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"v" = (/obj/item/brain,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"w" = (/obj/item/weapon/gun/energy/laser/captain,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"x" = (/obj/item/weapon/gun/projectile/deagle/gold,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"y" = (/obj/item/weapon/sord,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"z" = (/obj/item/weapon/gun/energy/lasercannon,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"A" = (/obj/item/weapon/gun/energy/crossbow/largecrossbow,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"B" = (/obj/item/weapon/gun/energy/pulse_rifle/destroyer,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"C" = (/obj/item/toy/spinningtoy,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"D" = (/obj/item/weapon/card/id/centcom,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"E" = (/obj/item/weapon/gun/grenadelauncher,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"F" = (/obj/item/weapon/chemsprayer,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) +"G" = (/obj/machinery/turret{dir = 8; lasers = 1; name = "Angry Turret"},/obj/machinery/light,/turf/simulated/floor{icon_state = "whiteshiny"},/area/turret_protected/AssistantRoom) + +(1,1,1) = {" +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaabbbbbbbbbbbbaaaaaaaaa +aaaaaaaaabccccccccccbaaaaaaaaa +aaaaaaaaabccddddddccbaaaaaaaaa +aaaaaaaaabcefghijklcbaaaaaaaaa +aaaaaaaaabcemnopqrlcbaaaaaaaaa +aaaaaaaaabcestquvqlcbaaaaaaaaa +aaaaaaaaabcewqqxqylcbaaaaaaaaa +aaaaaaaaabceqzAqBqlcbaaaaaaaaa +aaaaaaaaabceCqDqEFlcbaaaaaaaaa +aaaaaaaaabccGGGGGGccbaaaaaaaaa +aaaaaaaaabccccccccccbaaaaaaaaa +aaaaaaaaabbbbbbbbbbbbaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +"} diff --git a/maps/RandomZLevels/fileList.txt b/maps/RandomZLevels/fileList.txt new file mode 100644 index 000000000000..de6304c4d733 --- /dev/null +++ b/maps/RandomZLevels/fileList.txt @@ -0,0 +1,8 @@ +#List the potential random Z-levels here. +#Maps must be the full path to them +#Maps should be 255x255 or smaller and be bounded. Falling off the edge of the map will result in undefined behavior. +#Please ensure that your maps have a minimum distnace from the edge of 10 units, due to 'seamless transition' range +#SPECIFYING AN INVALID MAP WILL RESULT IN RUNTIMES ON GAME START + +#maps/RandomZLevels/assistantChamber.dmm + diff --git a/tgstation.dme b/tgstation.dme index c815c1ebd114..7444f6b345a2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -96,6 +96,7 @@ #define FILE_DIR "code/modules/detectivework" #define FILE_DIR "code/modules/flufftext" #define FILE_DIR "code/modules/food" +#define FILE_DIR "code/modules/maps" #define FILE_DIR "code/modules/mining" #define FILE_DIR "code/modules/mob" #define FILE_DIR "code/modules/mob/dead" @@ -178,6 +179,7 @@ #define FILE_DIR "icons/vending_icons" #define FILE_DIR "interface" #define FILE_DIR "maps" +#define FILE_DIR "maps/RandomZLevels" #define FILE_DIR "sound" #define FILE_DIR "sound/AI" #define FILE_DIR "sound/ambience" @@ -814,6 +816,11 @@ #include "code\modules\food\food.dm" #include "code\modules\food\meat.dm" #include "code\modules\food\recipes_microwave.dm" +#include "code\modules\maps\dmm_suite.dm" +#include "code\modules\maps\randomZlevel.dm" +#include "code\modules\maps\reader.dm" +#include "code\modules\maps\SwapMaps.dm" +#include "code\modules\maps\writer.dm" #include "code\modules\mining\machine_input_output_plates.dm" #include "code\modules\mining\machine_processing.dm" #include "code\modules\mining\machine_stacking.dm" @@ -1071,8 +1078,6 @@ #include "code\WorkInProgress\explosion_particles.dm" #include "code\WorkInProgress\BS12\uplink_kits.dm" #include "code\WorkInProgress\BS12\uplinks.dm" -#include "code\WorkInProgress\mapload\dmm_suite.dm" -#include "code\WorkInProgress\mapload\reader.dm" #include "code\WorkInProgress\organs\implants.dm" #include "code\WorkInProgress\organs\organs.dm" #include "interface\interface.dm"