diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm index 1a3fff351a66c8..6ee88dda1bb097 100644 --- a/code/game/objects/structures/flora.dm +++ b/code/game/objects/structures/flora.dm @@ -206,4 +206,27 @@ /obj/structure/flora/kirbyplants/dead name = "RD's potted plant" desc = "A gift from the botanical staff, presented after the RD's reassignment. There's a tag on it that says \"Y'all come back now, y'hear?\"\nIt doesn't look very healthy..." - icon_state = "plant-25" \ No newline at end of file + icon_state = "plant-25" + + +//a rock is flora according to where the icon file is +//and now these defines +/obj/structure/flora/rock + name = "rock" + desc = "a rock" + icon_state = "rock1" + icon = 'icons/obj/flora/rocks.dmi' + anchored = 1 + +/obj/structure/flora/rock/New() + ..() + icon_state = "rock[rand(1,5)]" + +/obj/structure/flora/rock/pile + name = "rocks" + desc = "some rocks" + icon_state = "rockpile1" + +/obj/structure/flora/rock/pile/New() + ..() + icon_state = "rockpile[rand(1,5)]" \ No newline at end of file diff --git a/code/game/turfs/unsimulated/floor.dm b/code/game/turfs/unsimulated/floor.dm index 1a4507dfd1fa9e..5799286f3ea6f3 100644 --- a/code/game/turfs/unsimulated/floor.dm +++ b/code/game/turfs/unsimulated/floor.dm @@ -17,4 +17,12 @@ icon_state = "engine" /turf/unsimulated/floor/attack_paw(user as mob) - return src.attack_hand(user) \ No newline at end of file + return src.attack_hand(user) + + +/turf/unsimulated/floor/grass + icon_state = "grass1" + +/turf/unsimulated/floor/grass/New() + ..() + icon_state = "grass[rand(1,4)]" \ No newline at end of file diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index f35c93a3e89a69..29944163e295a0 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -118,7 +118,8 @@ var/list/admin_verbs_debug = list( /client/proc/callproc_datum, /client/proc/SDQL2_query, /client/proc/test_movable_UI, - /client/proc/test_snap_UI + /client/proc/test_snap_UI, + /client/proc/debugNatureMapGenerator ) var/list/admin_verbs_possess = list( /proc/possess, diff --git a/code/modules/procedural mapping/mapGenerator.dm b/code/modules/procedural mapping/mapGenerator.dm new file mode 100644 index 00000000000000..60feecc29e0224 --- /dev/null +++ b/code/modules/procedural mapping/mapGenerator.dm @@ -0,0 +1,123 @@ + +/datum/mapGenerator + + //Map information + var/list/map = list() + var/turf/bottomLeft = null + var/turf/topRight = null + + //mapGeneratorModule information + var/list/modules = list() + +/datum/mapGenerator/New() + ..() + initialiseModules() + +//Defines the region the map represents, sets map, bottomLeft, topRight +//Returns the map +/datum/mapGenerator/proc/defineRegion(var/turf/Start, var/turf/End) + if(!checkRegion(Start, End)) + return 0 + + if(!Start || !End) + return 0 + bottomLeft = Start + topRight = End + + map = block(bottomLeft,topRight) + return map + + +//Checks for and Rejects bad region coordinates +//Returns 1/0 +/datum/mapGenerator/proc/checkRegion(var/turf/Start, var/turf/End) + . = 1 + + if(!Start || !End) + return 0 //Just bail + + if(Start.x > world.maxx || End.x > world.maxx) + . = 0 + if(Start.y > world.maxy || End.y > world.maxy) + . = 0 + if(Start.z > world.maxz || End.z > world.maxz) + . = 0 + + +//Requests the mapGeneratorModule(s) to (re)generate +/datum/mapGenerator/proc/generate() + set background = 1 //this can get beefy + + syncModules() + if(!modules || !modules.len) + return + for(var/datum/mapGeneratorModule/mod in modules) + mod.generate() + + +//Requests the mapGeneratorModule(s) to (re)generate this one turf +/datum/mapGenerator/proc/generateOneTurf(var/turf/T) + if(!T) + return + syncModules() + if(!modules || !modules.len) + return + for(var/datum/mapGeneratorModule/mod in modules) + mod.place(T) + + +//Replaces all paths in the module list with actual module datums +/datum/mapGenerator/proc/initialiseModules() + for(var/path in modules) + if(ispath(path)) + modules.Remove(path) + modules |= new path + syncModules() + + +//Sync mapGeneratorModule(s) to mapGenerator +/datum/mapGenerator/proc/syncModules() + for(var/datum/mapGeneratorModule/mod in modules) + mod.sync(src) + + + +/////////////////////////// +// HERE BE DEBUG DRAGONS // +/////////////////////////// + +/client/proc/debugNatureMapGenerator() + set name = "Test Nature Map Generator" + set category = "Debug" + + var/datum/mapGenerator/nature/N = new() + var/startInput = input(usr,"Start turf of Map, (X;Y;Z)", "Map Gen Settings", "1;1;1") as text + var/endInput = input(usr,"End turf of Map (X;Y;Z)", "Map Gen Settings", "[world.maxx];[world.maxy];[mob ? mob.z : 1]") as text + //maxx maxy and current z so that if you fuck up, you only fuck up one entire z level instead of the entire universe + if(!startInput || !endInput) + src << "Missing Input" + return + + var/list/startCoords = text2list(startInput, ";") + var/list/endCoords = text2list(endInput, ";") + if(!startCoords || !endCoords) + src << "Invalid Coords" + src << "Start Input: [startInput]" + src << "End Input: [endInput]" + return + + var/turf/Start = locate(text2num(startCoords[1]),text2num(startCoords[2]),text2num(startCoords[3])) + var/turf/End = locate(text2num(endCoords[1]),text2num(endCoords[2]),text2num(endCoords[3])) + if(!Start || !End) + src << "Invalid Turfs" + src << "Start Coords: [startCoords[1]] - [startCoords[2]] - [startCoords[3]]" + src << "End Coords: [endCoords[1]] - [endCoords[2]] - [endCoords[3]]" + return + + src << "Defining Region" + N.defineRegion(Start, End) + src << "Region Defined" + src << "Generating Region" + N.generate() + src << "Generated Region" + diff --git a/code/modules/procedural mapping/mapGeneratorModule.dm b/code/modules/procedural mapping/mapGeneratorModule.dm new file mode 100644 index 00000000000000..458f11cd9690be --- /dev/null +++ b/code/modules/procedural mapping/mapGeneratorModule.dm @@ -0,0 +1,101 @@ + +#define CLUSTER_CHECK_NONE 0 //No checks are done, cluster as much as possible +#define CLUSTER_CHECK_ATOMS 2 //Don't let atoms cluster, based on clusterMin and clusterMax as guides +#define CLUSTER_CHECK_TURFS 4 //Don't let turfs cluster, based on clusterMin and clusterMax as guides +#define CLUSTER_CHECK_ALL 6 //Don't let anything cluster, based on clusterMind and clusterMax as guides + +/datum/mapGeneratorModule + var/datum/mapGenerator/mother = null + var/list/spawnableAtoms = list() + var/list/spawnableTurfs = list() + var/clusterMax = 5 + var/clusterMin = 1 + var/clusterCheckFlags = CLUSTER_CHECK_ALL + + +//Syncs the module up with it's mother +/datum/mapGeneratorModule/proc/sync(var/datum/mapGenerator/mum) + mother = null + if(mum) + mother = mum + + +//Generates it's spawnable atoms and turfs +/datum/mapGeneratorModule/proc/generate() + if(!mother) + return + var/list/map = mother.map + for(var/turf/T in map) + place(T) + + +//Place a spawnable atom or turf on this turf +/datum/mapGeneratorModule/proc/place(var/turf/T) + if(!T) + return 0 + + var/clustering = 0 + + //Turfs don't care whether atoms can be placed here + for(var/turfPath in spawnableTurfs) + if(clusterCheckFlags & CLUSTER_CHECK_TURFS) + if(clusterMax && clusterMin) + clustering = rand(clusterMin,clusterMax) + if(locate(/atom/movable) in range(clustering, T)) + continue + if(prob(spawnableTurfs[turfPath])) + T.ChangeTurf(turfPath) + + //Atoms DO care whether atoms can be placed here + if(checkPlaceAtom(T)) + for(var/atomPath in spawnableAtoms) + if(clusterCheckFlags & CLUSTER_CHECK_ATOMS) + if(clusterMax && clusterMin) + clustering = rand(clusterMin,clusterMax) + if(locate(/atom/movable) in range(clustering, T)) + continue + if(prob(spawnableAtoms[atomPath])) + new atomPath (T) + + . = 1 + + +//Checks and Rejects dense turfs +/datum/mapGeneratorModule/proc/checkPlaceAtom(var/turf/T) + . = 1 + if(!T) + return 0 + if(T.density) + . = 0 + for(var/atom/A in T) + if(A.density) + . = 0 + break + + +/////////////////////////////////////////////////////////// +// PREMADE BASE TEMPLATES // +// Appropriate settings for usable types // +// Not usable types themselves, use them as parent types // +// Seriously, don't use these on their own, just parents // +/////////////////////////////////////////////////////////// +//The /atom and /turf examples are just so these compile, replace those with your typepaths in your subtypes. + +//Settings appropriate for a turf that covers the entire map region, eg a fill colour on a bottom layer in a graphics program. +//Should only have one of these in your mapGenerator unless you want to waste CPU +/datum/mapGeneratorModule/bottomLayer + clusterCheckFlags = CLUSTER_CHECK_NONE + spawnableAtoms = list()//Recommended: No atoms. + spawnableTurfs = list(/turf = 100) + +//Settings appropriate for turfs/atoms that cover SOME of the map region, sometimes referred to as a splatter layer. +/datum/mapGeneratorModule/splatterLayer + clusterCheckFlags = CLUSTER_CHECK_ALL + spawnableAtoms = list(/atom = 30) + spawnableTurfs = list(/turf = 30) + +//Settings appropriate for turfs/atoms that cover a lot of the map region, eg a dense forest. +/datum/mapGeneratorModule/denseLayer + clusterCheckFlags = CLUSTER_CHECK_NONE + spawnableAtoms = list(/atom = 75) + spawnableTurfs = list(/turf = 75) \ No newline at end of file diff --git a/code/modules/procedural mapping/mapGeneratorModules/nature.dm b/code/modules/procedural mapping/mapGeneratorModules/nature.dm new file mode 100644 index 00000000000000..09ca84574b1417 --- /dev/null +++ b/code/modules/procedural mapping/mapGeneratorModules/nature.dm @@ -0,0 +1,37 @@ + +//Contents exist primarily for the nature generator test type. + + +//Pine Trees +/datum/mapGeneratorModule/pineTrees + spawnableAtoms = list(/obj/structure/flora/tree/pine = 30) + +//Dead Trees +/datum/mapGeneratorModule/deadTrees + spawnableAtoms = list(/obj/structure/flora/tree/dead = 10) + +//Random assortment of bushes +/datum/mapGeneratorModule/randBushes + spawnableAtoms = list() + +/datum/mapGeneratorModule/randBushes/New() + ..() + spawnableAtoms = typesof(/obj/structure/flora/ausbushes) + for(var/i in spawnableAtoms) + spawnableAtoms[i] = 20 + + +//Random assortment of rocks and rockpiles +/datum/mapGeneratorModule/randRocks + spawnableAtoms = list(/obj/structure/flora/rock = 40, /obj/structure/flora/rock/pile = 20) + + +//Grass turfs +/datum/mapGeneratorModule/bottomLayer/grassTurfs + spawnableTurfs = list(/turf/unsimulated/floor/grass = 100) + + +//Grass tufts with a high spawn chance +/datum/mapGeneratorModule/denseLayer/grassTufts + spawnableTurfs = list() + spawnableAtoms = list(/obj/structure/flora/ausbushes/grassybush = 75) diff --git a/code/modules/procedural mapping/mapGeneratorReadme.dm b/code/modules/procedural mapping/mapGeneratorReadme.dm new file mode 100644 index 00000000000000..95fa449624e84d --- /dev/null +++ b/code/modules/procedural mapping/mapGeneratorReadme.dm @@ -0,0 +1,130 @@ + +/* +by RemieRichards + +////////////////////////////// +// CODER INFORMATIVE README // +////////////////////////////// +(See below for Mapper Friendly Readme) + +mapGenerator: + Desc: a mapGenerator is a master datum that collects + and syncs all mapGeneratorModules in it's modules list + + defineRegion(var/list/startList, var/list/endList) + Example: defineRegion(locate(1,1,1),locate(5,5,5)) + Desc: Sets the bounds of the mapGenerator's "map" + + checkRegion(var/turf/Start, var/turf/End) + Example: checkRegion(locate(1,1,1), locate(5,5,5)) + Desc: Checks if a rectangle between Start's coords and End's coords is valid + Existing Calls: mapGenerator/defineRegion() + + generate() + Example: generate() + Desc: Orders all mapGeneratorModules in the modules list to generate() + + generateOneTurf(var/turf/T) + Example: generateOneTurf(locate(1,1,1)) + Desc: Orders all mapGeneratorModules in the modules list to place(T) on this turf + + initialiseModules() + Example: initialiseModules() + Desc: Replaces all typepaths in the modules list with actual /datum/mapGenerator/Module types + Existing Calls: mapGenerator/New() + + syncModules() + Example: syncModules() + Desc: Sets the Mother variable on all mapGeneratorModules in the modules list to this mapGenerator + Existing Calls: initialiseModules(),generate(),generateOneTurf() + + +mapGeneratorModule + Desc: a mapGeneratorModule has spawnableAtoms and spawnableTurfs lists + which it will generate on turfs in it's mother's map based on cluster variables + + sync(var/datum/mapGenerator/mum) + Example: sync(a_mapGenerator_as_a_variable) + Desc: Sets the Mother variable to the mum argument + Existing Calls: mapGenerator/syncModules() + + generate() + Example: generate() + Desc: Calls place(T) on all turfs in it's mother's map + Existing Calls: mapGenerator/generate() + + place(var/turf/T) + Example: place(locate(1,1,1)) + Desc: Run this mapGeneratorModule's effects on this turf (Spawning atoms, Changing turfs) + Existing Calls: mapGenerator/generate(), mapGenerator/generateOneTurf() + + checkPlaceAtom(var/turf/T) + Example: checkPlace(locate(1,1,1)) + Desc: Checks if the turf is valid for placing atoms + Existing Calls: place() + + + +//////////////////////////// +// MAPPER FRIENDLY README // +//////////////////////////// + +Simple Workflow: + + 1. Define a/some mapGeneratorModule(s) to your liking, choosing atoms and turfs to spawn + #Note: I chose to split Turfs and Atoms off into seperate modules, but this is NOT required. + #Note: A mapGeneratorModule may have turfs AND atoms, so long as each is in it's appropriate list + + 2. Define a mapGenerator type who's modules list contains the typepath(s) of all the module(s) you wish to use + #Note: The order of the typepaths in the modules list is the order they will happen in, this is important for clusterCheckFlags. + + 3. Take notes of the Bottom Left and Top Right turfs of your rectangular "map"'s coordinates + #Note: X,Y AND Z, Yes you can created 3D "maps" by having differing Z coords + + 4. Create the mapGenerator type you created + + 5. Call yourMapGeneratorType.defineRegion(locate(X,Y,Z), locate(X,Y,Z)) + #Note: The above X/Y/Zs are the coordinates of the start and end turfs, the locate() simply finds the turf for the code + + 6. Call yourMapGeneratorType.generate(), this will cause all the modules in the generator to build within the map bounds + +Option Suggestions: + + * Have seperate modules for Turfs and Atoms, this is not enforced, but it is how I have structured my nature example. + * If your map doesn't look quite to your liking, simply jiggle with the variables on your modules and the type probabilities + * You can mix and map premade areas with the procedural generation, for example mapping an entire flat land but having code generate just the grass tufts + + +Using the Modules list + + Simply think of it like each module is a layer in a graphics editing program! + To help you do this templates such as /mapGeneratorModule/bottomLayer have been provided with appropriate default settings. + These are located near the bottom of mapGeneratorModule.dm + you would order your list left to right, top to bottom, e.g: + modules = list(bottomLayer,nextLayer,nextNextLayer) etc. + + +Variable Breakdown (For Mappers): + + mapGenerator + map - INTERNAL, do not touch + bottomLeft - INTERNAL, do not touch + topRight - INTERNAL, do not touch + modules - A list of typepaths of mapGeneratorModules + + mapGeneratorModule + mother - INTERNAL, do not touch + spawnableAtoms - A list of typepaths and their probability to spawn, eg: spawnableAtoms = list(/obj/structure/flora/tree/pine = 30) + spawnableTurfs - A list of typepaths and their probability to spawn, eg: spawnableTurfs = list(/turf/unsimulated/floor/grass = 100) + clusterMax - The max range to check for something being "too close" for this atom/turf to spawn, the true value is random between clusterMin and clusterMax + clusterMin - The min range to check for something being "too close" for this atom/turf to spawn, the true value is random between clusterMin and clusterMax + clusterCheckFlags - A Bitfield that controls how the cluster checks work. + + clusterCheckFlags flags: + CLUSTER_CHECK_NONE 0 //No checks are done, cluster as much as possible + CLUSTER_CHECK_ATOMS 2 //Don't let atoms cluster, based on clusterMin and clusterMax as guides + CLUSTER_CHECK_TURFS 4 //Don't let turfs cluster, based on clusterMin and clusterMax as guides + CLUSTER_CHECK_ALL 6 //Don't let anything cluster, based on clusterMind and clusterMax as guides + + +*/ \ No newline at end of file diff --git a/code/modules/procedural mapping/mapGenerators/nature.dm b/code/modules/procedural mapping/mapGenerators/nature.dm new file mode 100644 index 00000000000000..b699c802cd9613 --- /dev/null +++ b/code/modules/procedural mapping/mapGenerators/nature.dm @@ -0,0 +1,11 @@ + +//Exists primarily as a test type. + +/datum/mapGenerator/nature + modules = list(/datum/mapGeneratorModule/pineTrees, \ + /datum/mapGeneratorModule/deadTrees, \ + /datum/mapGeneratorModule/randBushes, \ + /datum/mapGeneratorModule/randRocks, \ + /datum/mapGeneratorModule/bottomLayer/grassTurfs, \ + /datum/mapGeneratorModule/denseLayer/grassTufts) + diff --git a/icons/obj/flora/rocks.dmi b/icons/obj/flora/rocks.dmi index a1f6a0df0a92f6..b03c866fd750c1 100644 Binary files a/icons/obj/flora/rocks.dmi and b/icons/obj/flora/rocks.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 848209caaa35d4..a74a3ce769439b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1274,6 +1274,11 @@ #include "code\modules\power\singularity\particle_accelerator\particle_power.dm" #include "code\modules\power\supermatter\supermatter.dm" #include "code\modules\power\supermatter\supermatter_crate.dm" +#include "code\modules\procedural mapping\mapGenerator.dm" +#include "code\modules\procedural mapping\mapGeneratorModule.dm" +#include "code\modules\procedural mapping\mapGeneratorReadme.dm" +#include "code\modules\procedural mapping\mapGeneratorModules\nature.dm" +#include "code\modules\procedural mapping\mapGenerators\nature.dm" #include "code\modules\projectiles\ammunition.dm" #include "code\modules\projectiles\firing.dm" #include "code\modules\projectiles\gun.dm"