Download CommonLua/MapGen/PrefabMarker.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 35.8 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/MapGen/PrefabMarker.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/MapGen/PrefabMarker.lua
-
curl -L -o PrefabMarker.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/MapGen/PrefabMarker.lua
35.8 kB
| local type_tile = const.TypeTileSize | |
| local height_tile = const.HeightTileSize | |
| local grass_tile = const.GrassTileSize | |
| local height_scale = const.TerrainHeightScale | |
| local empty_table = empty_table | |
| local GetClassFlags = CObject.GetClassFlags | |
| local SetGameFlags = CObject.SetGameFlags | |
| local developer = Platform.developer and Platform.desktop | |
| local unpack = table.unpack | |
| local def_size = point(20*guim, 20*guim) | |
| local capture_items = {"Height", "Terrain", "Grass"} | |
| local def_capt = set(table.unpack(capture_items)) | |
| local mask_max = 255 | |
| local invalid_type_value = 255 | |
| local invalid_grass_value = 255 | |
| local transition_max_pct = 30 | |
| function GetTerrainGridsMaxGranularity() | |
| local granularity = Max(grass_tile, type_tile, height_tile) | |
| assert(granularity % grass_tile == 0) | |
| assert(granularity % type_tile == 0) | |
| assert(granularity % height_tile == 0) | |
| return granularity | |
| end | |
| local function ApplyHeightOpCombo(first) | |
| local items = { | |
| {value = '=', text = "Equals"}, | |
| {value = '<', text = "Min"}, | |
| {value = '>', text = "Max"}, | |
| {value = '~', text = "Average"}, | |
| {value = '+', text = "Add"}, | |
| } | |
| if first then | |
| table.insert(items, 1, first) | |
| end | |
| return items | |
| end | |
| local post_process_items = { | |
| { value = 0, text = "" }, | |
| { value = 1, text = "fill holes"}, | |
| { value = 2, text = "adjust capture"}, | |
| } | |
| assert(type_tile == const.HeightTileSize) | |
| if FirstLoad then | |
| PrefabMarkers = {} | |
| ExportedPrefabs = {} | |
| PrefabTypes = {} | |
| PrefabTypeToPrefabs = {} | |
| PrefabDimensions = empty_table | |
| end | |
| PrefabMarkerVersion = '1' | |
| local p_dprint = CreatePrint{ "RM", format = print_format, output = DebugPrint } | |
| local p_print = developer and CreatePrint{ "RM", format = print_format, color = yellow} or p_dprint | |
| function GetPrefabFileObjs(name) | |
| return string.format("Prefabs/%s.bin", name) | |
| end | |
| function GetPrefabFileHeight(name) | |
| return string.format("Prefabs/%s.h.grid", name) | |
| end | |
| function GetPrefabFileType(name) | |
| return string.format("Prefabs/%s.t.grid", name) | |
| end | |
| function GetPrefabFileGrass(name) | |
| return string.format("Prefabs/%s.g.grid", name) | |
| end | |
| function GetPrefabFileMask(name) | |
| return string.format("Prefabs/%s.m.grid", name) | |
| end | |
| local function GetRotationModesCombo() | |
| return { | |
| { value = false, text = "" }, | |
| { value = "slope", text = "Follow Slope Angle" }, | |
| { value = "map", text = "Follow Map Orientation" }, | |
| } | |
| end | |
| local function GetPoiAreas(self) | |
| local names = {} | |
| local poi_preset = PrefabPoiToPreset[self.PoiType] | |
| for _, group in pairs(poi_preset and poi_preset.PrefabTypeGroups) do | |
| names[#names + 1] = group.id | |
| end | |
| table.sort(names) | |
| return names | |
| end | |
| local function GetPoiAreasCount(self) | |
| local poi_preset = PrefabPoiToPreset[self.PoiType] | |
| return #(poi_preset and poi_preset.PrefabTypeGroups or "") | |
| end | |
| ---- | |
| DefineClass.PrefabObj = { | |
| __parents = { "Object", "EditorVisibleObject" }, | |
| flags = { efWalkable = false, efCollision = false, efApplyToGrids = false }, | |
| Scale = 250, | |
| } | |
| function PrefabObj:Init() | |
| self:SetScale(self.Scale) | |
| self:SetVisible(IsEditorActive()) | |
| end | |
| ---- | |
| DefineClass.DebugOverlayControl = { | |
| __parents = { "PropertyObject" }, | |
| } | |
| ---- | |
| DefineClass.PrefabMarker = { | |
| __parents = { "MapMarkerObj", "PrefabObj", "DebugOverlayControl" }, | |
| flags = { efWalkable = false, efApplyToGrids = false, efCollision = false, gofAlwaysRenderable = true }, | |
| entity = "WayPointBig", | |
| properties = { | |
| { id = "PrefabName", name = "Name", editor = "text", default = "", category = "Prefab", read_only = true, dont_save = true, }, | |
| { id = "ExportedName", name = "Exported", editor = "text", default = "", category = "Prefab", read_only = true, buttons = {{name = "Export", func = "ActionPrefabExport"}, {name = "Revision", func = "ActionPrefabRevision"}, {name = "Folder", func = "ActionExploreTo"}}}, | |
| { id = "ExportError", name = "Export Error", editor = "text", default = "", category = "Prefab", read_only = true, no_edit = function(self) return self.ExportError == "" end }, | |
| { id = "ExportedHash", editor = "number", default = false, category = "Prefab", export = "hash", no_edit = true }, | |
| { id = "AssetsRevision", name = "Assets Revision", editor = "number", default = false, category = "Prefab", export = "revision", read_only = true }, | |
| { id = "PrefabType", name = "Type", editor = "preset_id", default = "", category = "Prefab", export = "type", compatibility = true, preset_class = "PrefabType", }, | |
| { id = "PrefabWeight", name = "Weight", editor = "number", default = 100, category = "Prefab", export = "weight", compatibility = true, min = 0 }, | |
| { id = "PrefabMaxCount", name = "Max Count", editor = "number", default = -1, category = "Prefab", export = "max_count", compatibility = true }, | |
| { id = "RepeatReduct", name = "Repeat Reduct (%)", editor = "number", default = 0, category = "Prefab", export = "repeat_reduct", compatibility = true, min = 0, max = 100, slider = true, no_edit = function(self) return self.PoiType ~= "" end }, | |
| { id = "PrefabOrient", name = "Orientation", editor = "point", default = axis_x, category = "Prefab", helper = "relative_pos", compatibility = true, helper_outside_object = true, use_object = true, help = "Used to specify a common orientation for a set of prefabs." }, | |
| { id = "PrefabAngle", name = "Angle (deg)", editor = "number", default = 0, category = "Prefab", export = "angle", compatibility = true, scale = 60, read_only = true, dont_save = true }, | |
| { id = "PrefabAngleVar", name = "Angle Variate (deg)", editor = "number", default = 180*60, category = "Prefab", export = "angle_variation", compatibility = true, min = 0, max = 180*60, slider = true, scale = 60, help = "Random variation around the prefab angle" }, | |
| { id = "PrefabRotateMode", name = "Rotation Mode", editor = "choice", default = "map", category = "Prefab", export = "rotation_mode", compatibility = true, items = GetRotationModesCombo }, | |
| { id = "DecorObstruct", name = "Decor Obstruct", editor = "bool", default = false, category = "Prefab", export = "decor_obstruct", compatibility = true, help = "Obstruct placement of other decor prefabs in its area. Option valid only when placed as decor." }, | |
| { id = "SaveCollections", name = "Important Collections", editor = "bool", default = false, category = "Prefab", export = "save_collections", help = "If specified, the collections in the prefab will be persisted after the generation." }, | |
| { id = "Tags", name = "Tags", editor = "set", default = empty_table, category = "Prefab", export = "tags", compatibility = true, items = function() return PrefabTagsCombo() end, help = "Keywords used to group similar prefabs" }, | |
| { id = "AllTags", name = "All Tags", editor = "text", default = "", category = "Prefab", read_only = true, dont_save = true, help = "Includes the tags inherited from prefab type and POI type" }, | |
| { id = "PoiType", name = "POI Type", editor = "preset_id", default = "", category = "Prefab", export = "poi_type", compatibility = true, preset_class = "PrefabPOI", }, | |
| { id = "PoiArea", name = "POI Area", editor = "choice", default = "", category = "Prefab", export = "poi_area", compatibility = true, items = GetPoiAreas, no_edit = function(self) return GetPoiAreasCount(self) ==0 end, help = "Disregard prefab type and use POI area instead." }, | |
| { id = "CaptureSet", name = "Capture", editor = "set", default = def_capt, category = "Terrain", items = capture_items }, | |
| { id = "CaptureSize", name = "Size", editor = "point", default = def_size, category = "Terrain", export = "size", scale = "m", min = 0, granularity = GetTerrainGridsMaxGranularity(), | |
| helper = "terrain_rect", terrain_rect_color = RGBA(64, 196, 0, 96), terrain_rect_step = guim/2, terrain_rect_zoffset = guim/4, terrain_rect_depth_test = true, terrain_rect_grid = true, | |
| buttons = { {name = "Capture", func = "ActionCaptureTerrain"}, {name = "Clear", func = "ActionClearTerrain"}, }, | |
| }, | |
| { id = "Centered", name = "Centered", editor = "bool", default = false, category = "Terrain", compatibility = true, help = "Specify if the marker is in the center of the capture area"}, | |
| { id = "HeightOp", name = "Apply Height", editor = "dropdownlist", default = '+', category = "Terrain", export = "height_op", items = function() return ApplyHeightOpCombo() end, help = "Specify how the captured terrain height would be applied over the existing" }, | |
| { id = "InvalidTerrain", name = "Invalid Terrain", editor = "dropdownlist", category = "Terrain", items = function() return GetTerrainNamesCombo() end, help = "Tiles with invalid terrain type wont be captured" }, | |
| { id = "InvalidGrass", name = "Invalid Grass", editor = "number", default = -1, category = "Terrain", help = "Tiles with invalid grass density wont be captured. The final values will be remapped if possible to fit the whole density range." }, | |
| { id = "SkippedTerrains", name = "Skipped Terrains", editor = "string_list", default = false, category = "Terrain", items = function() return GetTerrainNamesCombo() end, help = "Tiles with invalid terrain type wont be captured" }, | |
| { id = "ApplyTerrain", name = "Apply Terrain", editor = "dropdownlist", default = '', category = "Terrain", items = {'', 'invalid'}, help = "Specify how the captured terrain type would be applied over the existing", }, | |
| { id = "TransitionZone", name = "Transition Zone", editor = "number", default = 64*guim, category = "Terrain", compatibility = true, min = 0, max = function(o) return o:GetMaxTransitionDist() end, slider = true, scale = "m", granularity = type_tile, help = "Transition zone for smooth stitching" }, | |
| { id = "CircleMask", name = "Circle Mask", editor = "bool", default = false, category = "Terrain",}, | |
| { id = "CircleMaskRadius", name = "Custom Mask Radius",editor = "number", default = false, category = "Terrain", scale = "m", no_edit = PropChecker("CircleMask", false) }, | |
| { id = "PostProcess", name = "Post Process", editor = "dropdownlist", default = 2, category = "Terrain", items = post_process_items, no_edit = function(self) return self.CircleMask end}, | |
| { id = "TerrainPreview", name = "Terrain Preview", editor = "bool", default = true, category = "Terrain" }, | |
| { id = "HeightMap", name = "Height Map", editor = "grid", default = false, category = "Terrain", read_only = true, dont_save = true, min = 128, max = 256, no_edit = function(self) return not self.TerrainPreview end, grid_offset = function(self) return self.HeightOffset end }, | |
| { id = "HeightHash", editor = "number", default = false, category = "Terrain", export = "height_hash", no_edit = true }, | |
| { id = "HeightOffset", editor = "number", default = 0, category = "Terrain", export = "height_offset", no_edit = true }, | |
| { id = "HeightMin", editor = "point", default = point30, category = "Terrain", export = "min", no_edit = true }, | |
| { id = "HeightMax", editor = "point", default = point30, category = "Terrain", export = "max", no_edit = true }, | |
| { id = "TypeMap", name = "Terrain Type", editor = "grid", default = false, category = "Terrain", read_only = true, dont_save = true, min = 128, max = 256, color = true, invalid_value = invalid_type_value, no_edit = function(self) return not self.TerrainPreview end, }, | |
| { id = "TypeHash", editor = "number", default = false, category = "Terrain", export = "type_hash", no_edit = true }, | |
| { id = "TypeNames", editor = "prop_table", default = false, category = "Terrain", export = "type_names", no_edit = true,}, | |
| { id = "GrassMap", name = "Grass", editor = "grid", default = false, category = "Terrain", read_only = true, dont_save = true, min = 128, max = 256, invalid_value = invalid_grass_value, no_edit = function(self) return not self.TerrainPreview end, }, | |
| { id = "GrassHash", editor = "number", default = false, category = "Terrain", export = "grass_hash", no_edit = true }, | |
| { id = "MaskMap", name = "Transition Mask", editor = "grid", default = false, category = "Terrain", read_only = true, dont_save = true, min = 128, max = 256, no_edit = function(self) return not self.TerrainPreview end, }, | |
| { id = "MaskHash", editor = "number", default = false, category = "Terrain", export = "mask_hash", no_edit = true }, | |
| { id = "RequiredMemory", name = "Required Memory (KB)", editor = "number", default = 0, category = "Stats", export = "required_memory", read_only = true, scale = 1024 }, | |
| { id = "TerrainCaptureTime", name = "Terrain Capture (ms)", editor = "number", default = 0, category = "Stats", read_only = true, dont_save = true }, | |
| { id = "PlayArea", editor = "number", default = 0, category = "Stats", export = "play_area", compatibility = true, no_edit = true }, | |
| { id = "TotalArea", editor = "number", default = 0, category = "Stats", export = "total_area", compatibility = true, no_edit = true }, | |
| { id = "RadiusMin", editor = "number", default = 0, category = "Stats", export = "min_radius", compatibility = true, no_edit = true }, | |
| { id = "RadiusMax", editor = "number", default = 0, category = "Stats", export = "max_radius", compatibility = true, no_edit = true }, | |
| { id = "PlayAreaRatio", name = "Play Area (%)", editor = "number", default = 0, category = "Stats", read_only = true, dont_save = true }, | |
| { id = "MapTotalArea", name = "Total Area (m^2)", editor = "number", default = 0, category = "Stats", scale = guim*guim, read_only = true, dont_save = true }, | |
| { id = "MapRadiusMin", name = "Min Radius (m)", editor = "number", default = 0, category = "Stats", scale = guim, read_only = true, dont_save = true }, | |
| { id = "MapRadiusMax", name = "Max Radius (m)", editor = "number", default = 0, category = "Stats", scale = guim, read_only = true, dont_save = true }, | |
| { id = "HeightRougness", name = "Height Rougness", editor = "number", default = 0, category = "Stats", read_only = true, help = "Quantative estimation of the maximum height map roughness" }, | |
| { id = "ObjCount", name = "Obj Count", editor = "number", default = 0, category = "Stats", export = "obj_count", compatibility = true, read_only = true, help = "Relative to the maximum allowed object density" }, | |
| { id = "ObjMaxCount", name = "Obj Max Count", editor = "number", default = 0, category = "Stats", read_only = true, help = "Maximum allowed objects for the current size of the prefab" }, | |
| { id = "ObjRadiusMin", name = "Obj Radius Min (m)", editor = "number", default = 0, category = "Stats", export = "obj_min_radius", read_only = true, scale = guim, }, | |
| { id = "ObjRadiusMax", name = "Obj Radius Max (m)", editor = "number", default = 0, category = "Stats", export = "obj_max_radius", read_only = true, scale = guim, }, | |
| { id = "ObjRadiusAvg", name = "Obj Radius Avg (m)", editor = "number", default = 0, category = "Stats", export = "obj_avg_radius", read_only = true, scale = guim, }, | |
| { id = "NestedColls", name = "Nested Collections", editor = "prop_table", default = false, category = "Stats", export = "nested_colls", read_only = true, indent = ' '}, | |
| { id = "NestedOptObjs", name = "Nested Optional Objs", editor = "prop_table", default = false, category = "Stats", export = "nested_opt_objs", read_only = true, indent = ' '}, | |
| }, | |
| InvalidTerrain = "", -- will be assigned later from the constant defs | |
| } | |
| function OnMsg.ClassesGenerate() | |
| PrefabMarker.InvalidTerrain = table.get(const, "Prefab", "InvalidTerrain") or "" | |
| end | |
| function PrefabMarker:GetPrefabAngle() | |
| return CalcOrientation(self.PrefabOrient) | |
| end | |
| local function PrefabFilter(pstyles, ptypes, revision, version) | |
| revision = revision or AssetsRevision | |
| version = version or max_int | |
| local matched | |
| local markers = PrefabMarkers | |
| for i=1,#markers do | |
| local marker = markers[i] | |
| if (not pstyles or marker.style == "" or pstyles[marker.style]) | |
| and (not ptypes or marker.type == "" or ptypes[marker.type]) | |
| and revision >= (marker.revision or 0) | |
| and version >= (marker.version or 1) then | |
| matched = matched or {} | |
| matched[#matched + 1] = marker | |
| end | |
| end | |
| return matched or empty_table | |
| end | |
| function PrefabMarker:GetPlayAreaRatio() | |
| return self.TotalArea > 0 and MulDivRound(100, self.PlayArea, self.TotalArea) or 0 | |
| end | |
| function PrefabMarker:GetMapTotalArea() | |
| return self.TotalArea * type_tile * type_tile | |
| end | |
| function PrefabMarker:GetMapRadiusMin() | |
| return self.RadiusMin * type_tile | |
| end | |
| function PrefabMarker:GetMapRadiusMax() | |
| return self.RadiusMax * type_tile | |
| end | |
| function PrefabComposeName(props) | |
| local prefab_name = props.name or "" | |
| if #prefab_name > 0 then | |
| local prefab_type = props.type or "" | |
| if prefab_type == "" then | |
| prefab_type = "Any" | |
| end | |
| prefab_name = prefab_type .. "." .. prefab_name | |
| local prefab_style = props.style or "" | |
| if #prefab_style ~= 0 then | |
| prefab_name = prefab_style .. "." .. prefab_name | |
| end | |
| end | |
| return prefab_name | |
| end | |
| function PrefabMarker:GetPrefabName() | |
| return PrefabComposeName{name = self.MarkerName, type = self.PrefabType} | |
| end | |
| function PrefabMarker:GetAllTags() | |
| local poi_tags = table.get(PrefabPoiToPreset, self.PoiType, "Tags") | |
| local ptype_tags = table.get(PrefabTypeToPreset, self.PrefabType, "Tags") | |
| local marker_tags = self.Tags | |
| local tags = {} | |
| table.append(tags, ptype_tags) | |
| table.append(tags, marker_tags) | |
| table.append(tags, poi_tags) | |
| return table.concat(table.keys(tags, true), ", ") | |
| end | |
| ---- | |
| function GetTypeRemapping(name_to_idx) | |
| local type_remapping | |
| local TerrainTextures = TerrainTextures | |
| local GetTerrainTextureIndex = GetTerrainTextureIndex | |
| for name, idx in pairs(name_to_idx or empty_table) do | |
| if TerrainTextures[idx].id ~= name then | |
| local new_idx = GetTerrainTextureIndex(name) | |
| if not new_idx then | |
| assert(false, "No such terrain type: " .. name) | |
| else | |
| type_remapping = type_remapping or {} | |
| type_remapping[idx] = new_idx | |
| end | |
| end | |
| end | |
| if type_remapping then | |
| for i = 0, MaxTerrainTextureIdx() do | |
| type_remapping[i] = type_remapping[i] or i | |
| end | |
| end | |
| return type_remapping | |
| end | |
| function PrefabMarker:GetMaxTransitionDist() | |
| local min_size = Min(self.CaptureSize:xy()) | |
| return Min(type_tile * mask_max, min_size * transition_max_pct / 100) | |
| end | |
| function PrefabMarker:GetTransitionDist() | |
| return Max(0, Min(self.TransitionZone, self:GetMaxTransitionDist())) | |
| end | |
| ---- | |
| local function PrefabUpdateExported() | |
| ExportedPrefabs = {} | |
| local err, list = AsyncListFiles("Prefabs", "*.bin") | |
| if err then | |
| p_print("PrefabUpdateExported: ", err) | |
| return | |
| end | |
| for i=1,#list do | |
| local file = list[i] | |
| local dir, name, ext = SplitPath(file) | |
| if ExportedPrefabs[name] then | |
| p_print("Duplicated exported prefab name:", name) | |
| else | |
| ExportedPrefabs[name] = true | |
| end | |
| end | |
| end | |
| function PrefabCalcStats(prefabs) | |
| local min_prefab_radius, max_prefab_radius = max_int, min_int | |
| local avg_prefab_radius, radius_prefabs = 0, 0 | |
| local min_height, max_height = max_int, min_int | |
| local avg_max_height, avg_min_height, height_prefabs = 0, 0, 0 | |
| local min_play_area, max_play_area = max_int, min_int | |
| local avg_play_area, play_prefabs = 0, 0 | |
| local function get_avg(avg, count, value) | |
| return (avg * count + value) / (count + 1) | |
| end | |
| for i = 1,#prefabs do | |
| local prefab = prefabs[i] | |
| local play_area = prefab.play_area or 0 | |
| if play_area > 0 then | |
| min_play_area = Min(min_play_area, play_area) | |
| max_play_area = Max(max_play_area, play_area) | |
| avg_play_area = get_avg(avg_play_area, play_prefabs, play_area) | |
| play_prefabs = play_prefabs + 1 | |
| end | |
| local radius = prefab.max_radius or 0 | |
| if radius > 0 then | |
| min_prefab_radius = Min(min_prefab_radius, radius) | |
| max_prefab_radius = Max(max_prefab_radius, radius) | |
| avg_prefab_radius = get_avg(avg_prefab_radius, radius_prefabs, radius) | |
| radius_prefabs = radius_prefabs + 1 | |
| end | |
| if prefab.max and prefab.min then | |
| max_height = Max(max_height, prefab.max:z()) | |
| min_height = Min(min_height, prefab.min:z()) | |
| avg_max_height = get_avg(avg_max_height, height_prefabs, max_height) | |
| avg_min_height = get_avg(avg_min_height, height_prefabs, min_height) | |
| height_prefabs = height_prefabs + 1 | |
| end | |
| end | |
| return { | |
| MinRadius = min_prefab_radius, | |
| MaxRadius = max_prefab_radius, | |
| AvgRadius = avg_prefab_radius, | |
| MinPlayArea = min_play_area, | |
| MaxPlayArea = max_play_area, | |
| AvgPlayArea = avg_play_area, | |
| MaxHeight = max_height, | |
| MinHeight = min_height, | |
| AvgMaxHeight = avg_max_height, | |
| AvgMinHeight = avg_min_height, | |
| } | |
| end | |
| local function ConvertTags(tags) | |
| return tags and table.invert(tags) or nil | |
| end | |
| function PrefabUpdateMarkers() | |
| local markers = Markers | |
| local hash_keys = {"hash", "height_hash", "type_hash", "mask_hash"} | |
| local versions = {} | |
| local prefabs = {} | |
| local deprecated = 0 | |
| local type_to_prefabs = {} | |
| local defaults = {} | |
| for _, prop in ipairs(PrefabMarker:GetProperties()) do | |
| local export_id = prop.export | |
| if export_id then | |
| defaults[export_id] = PrefabMarker:GetProperty(prop.id) | |
| end | |
| end | |
| local PrefabTypeToPreset = PrefabTypeToPreset | |
| local ExportedPrefabs = ExportedPrefabs | |
| local PrefabComposeName = PrefabComposeName | |
| local prefab_meta = { __index = defaults } | |
| local any_type = {} | |
| for i = 1,#markers do | |
| local marker = markers[i] | |
| local data = marker.type == "Prefab" and marker.data | |
| if data then | |
| local prefab = dostring(data) | |
| if not prefab then | |
| p_print("Prefab", marker.name, "unserialize props error!") | |
| else | |
| local name = PrefabComposeName(prefab) | |
| if not ExportedPrefabs[name] then | |
| p_print("No exported prefab found ", name, "on", marker.map, "at", marker.pos) | |
| elseif prefabs[name] then | |
| local prev = prefabs[name] | |
| p_print("Duplicated prefabs:\n\t1.", name, "on", marker.map, "at", marker.pos, "\n\t2.", prefabs[prev], "on", prev.marker.map, "at", prev.marker.pos) | |
| else | |
| local ptype = prefab.type or "" | |
| if ptype == "" or PrefabTypeToPreset[ptype] then | |
| setmetatable(prefab, prefab_meta) | |
| prefabs[#prefabs + 1] = prefab | |
| prefabs[name] = prefab | |
| prefabs[prefab] = name | |
| local type_prefabs = ptype == "" and any_type or type_to_prefabs[ptype] | |
| if type_prefabs then | |
| type_prefabs[#type_prefabs + 1] = prefab | |
| else | |
| type_to_prefabs[prefab.type] = {prefab} | |
| end | |
| if marker.data_version ~= PrefabMarkerVersion then | |
| deprecated = deprecated + 1 | |
| end | |
| local version = prefab.version or 1 | |
| local info = versions[version] or {count = 0} | |
| versions[version] = info | |
| info.count = info.count + 1 | |
| for _, key in ipairs(hash_keys) do | |
| info[key] = xxhash(prefab[key], info[key]) | |
| end | |
| prefab.marker = marker | |
| end | |
| end | |
| end | |
| end | |
| end | |
| for ptype, prefabs in ipairs(type_to_prefabs) do | |
| table.iappend(prefabs, any_type) | |
| end | |
| if deprecated > 0 then | |
| p_print(deprecated, "deprecated prefabs need re-export") | |
| end | |
| if const.RandomMap.PrefabVersionLog then | |
| p_dprint("Prefab marker versions:", TableToLuaCode(versions)) | |
| end | |
| table.sort(prefabs, function(a, b) return prefabs[a] < prefabs[b] end) | |
| PrefabMarkers = prefabs | |
| PrefabDimensions = PrefabCalcStats(prefabs) | |
| PrefabTypeToPrefabs = type_to_prefabs | |
| PrefabTypes = table.keys(type_to_prefabs, true) | |
| Msg("PrefabMarkersChanged") | |
| DelayedCall(0, ReloadShortcuts) | |
| end | |
| function PrefabSaveCmp(cmp_version, filename, cmp_fmt, cmp_props) | |
| filename = filename or "cmp.txt" | |
| cmp_props = cmp_props or {"hash", "height_hash", "type_hash", "mask_hash"} | |
| cmp_fmt = cmp_fmt or "%40s | %12s | %12s | %12s | %12s |\n" | |
| local props = {} | |
| local cmp_list = {string.format(cmp_fmt, "name", unpack(cmp_props)), string.rep('-', 120), "\n"} | |
| for i, marker in ipairs(PrefabMarkers) do | |
| if not cmp_version or (marker.version or 1) == cmp_version then | |
| local name = PrefabMarkers[marker] or "" | |
| for i, key in ipairs(cmp_props) do | |
| props[i] = marker[key] or "" | |
| end | |
| cmp_list[#cmp_list + 1] = string.format(cmp_fmt, name, unpack(props)) | |
| end | |
| end | |
| return AsyncStringToFile(filename, cmp_list) | |
| end | |
| function OnMsg.DataLoaded() | |
| CreateRealTimeThread(function() | |
| PrefabUpdateExported() | |
| PrefabUpdateMarkers() | |
| end) | |
| end | |
| function PrefabPreload(prefab, params_meta, skip) | |
| local name = PrefabMarkers[prefab] | |
| if not ExportedPrefabs[name] then | |
| p_print("No such exported prefab", name) | |
| return | |
| end | |
| local load_err, height_grid, type_grid, grass_grid, mask_grid, type_remapping, height_op, height_offset | |
| local skip_height = skip and skip.Height | |
| local height_file = not skip_height and prefab.height_hash and GetPrefabFileHeight(name) | |
| if height_file then | |
| height_grid, load_err = GridReadFile(height_file) | |
| if load_err then | |
| p_print("Failed to load height map of", name, ":", load_err or "failed") | |
| return | |
| end | |
| if developer and xxhash(height_grid) ~= prefab.height_hash then | |
| p_print("Detected changes in the height map of", name) | |
| end | |
| height_op = prefab.height_op | |
| height_offset = prefab.height_offset | |
| end | |
| local skip_type = skip and skip.Type | |
| local type_file = not skip_type and prefab.type_hash and GetPrefabFileType(name) | |
| if type_file then | |
| type_grid, load_err = GridReadFile(type_file) | |
| if load_err then | |
| p_print("Failed to load type map of", name, ":", load_err or "failed") | |
| return | |
| end | |
| if developer and xxhash(type_grid) ~= prefab.type_hash then | |
| p_print("Detected changes in the type map of", name) | |
| end | |
| type_remapping = GetTypeRemapping(prefab.type_names) | |
| end | |
| local skip_grass = skip and skip.Grass | |
| local grass_file = not skip_grass and prefab.grass_hash and GetPrefabFileGrass(name) | |
| if grass_file then | |
| grass_grid, load_err = GridReadFile(grass_file) | |
| if load_err then | |
| p_print("Failed to load grass map of", name, ":", load_err or "failed") | |
| return | |
| end | |
| if developer and xxhash(grass_grid) ~= prefab.grass_hash then | |
| p_print("Detected changes in the grass map of", name) | |
| end | |
| end | |
| local mask_file = prefab.mask_hash and GetPrefabFileMask(name) | |
| if mask_file then | |
| mask_grid, load_err = GridReadFile(mask_file) | |
| if load_err then | |
| p_print("Failed to load mask of", name, ":", load_err or "failed") | |
| return | |
| end | |
| if developer and xxhash(mask_grid) ~= prefab.mask_hash then | |
| p_print("Detected changes in the mask of", name) | |
| end | |
| end | |
| local params = { | |
| height_grid = height_grid, | |
| height_op = height_op, | |
| height_offset = height_offset, | |
| type_grid = type_grid, | |
| type_remapping = type_remapping, | |
| grass_grid = grass_grid, | |
| mask_grid = mask_grid, | |
| } | |
| if params_meta then | |
| setmetatable(params, params_meta) | |
| end | |
| return params | |
| end | |
| function PlacePrefab(name, prefab_pos, prefab_angle, seed, place_params) | |
| if (name or "") == "" or not ExportedPrefabs[name] then | |
| return "no exported prefab found" | |
| end | |
| local filename = GetPrefabFileObjs(name) | |
| local err, bin = AsyncFileToString(filename, nil, nil, "pstr") | |
| if err then | |
| return err | |
| end | |
| local defs = Unserialize(bin) | |
| if not defs then | |
| return "Failed to unserialize objects" | |
| end | |
| local prefab = PrefabMarkers[name] | |
| if not prefab then | |
| return "no such prefab marker" | |
| end | |
| local raster_params = PrefabPreload(prefab) | |
| if not raster_params then | |
| return "prefab loading failed" | |
| end | |
| local existing_objs = MapGet(prefab_pos, prefab.max_radius * type_tile, "attached", false, function(obj) | |
| return not IsClutterObj(obj) and GetClassFlags(obj, const.cfCodeRenderable) == 0 | |
| end) or {} | |
| place_params = place_params or empty_table | |
| local change_height = not not raster_params.height_grid | |
| local change_type = not not raster_params.type_grid | |
| local change_grass = not not raster_params.grass_grid | |
| local create_undo = place_params.create_undo | |
| if create_undo then | |
| XEditorUndo:BeginOp{ | |
| name = "PlacePrefab", | |
| height = change_height, | |
| terrain_type = change_type, | |
| grass_density = change_grass, | |
| objects = existing_objs, | |
| } | |
| end | |
| prefab_angle = (prefab_angle or 0) - (prefab.angle or 0) | |
| local inv_bbox | |
| if change_height or change_type or change_grass then | |
| raster_params.pos = prefab_pos | |
| raster_params.angle = prefab_angle | |
| raster_params.dither_seed = seed | |
| err, inv_bbox = AsyncGridSetTerrain(raster_params) | |
| if err then | |
| if create_undo then | |
| XEditorUndo:EndOp(existing_objs) | |
| end | |
| return "failed to apply terrain" | |
| elseif inv_bbox then | |
| if change_height then | |
| terrain.InvalidateHeight(inv_bbox) | |
| end | |
| if change_type then | |
| terrain.InvalidateType(inv_bbox) | |
| end | |
| end | |
| end | |
| local gof = const.gofPermanent | const.gofGenerated | |
| local cofComponentRandomMap = const.cofComponentRandomMap | |
| local g_Classes = g_Classes | |
| local GetPrefabObjPos, SetPrefabObjPos = GetPrefabObjPos, SetPrefabObjPos | |
| local PropObjSetProperty = PropObjSetProperty | |
| local dont_clamp_objects = place_params.dont_clamp_objects | |
| local ignore_ground_offset = place_params.ignore_ground_offset | |
| local fadein = place_params.fadein | |
| local save_collections = prefab.save_collections | |
| local placed_cols = 0 | |
| local remap_col_idx, last_col_idx | |
| local objs = {} | |
| local base_prop_count = const.RandomMap.PrefabBasePropCount | |
| SuspendPassEdits("PlacePrefab") | |
| for _, def in ipairs(defs) do | |
| local class, dpos, angle, daxis, | |
| scale, rmf_flags, fade_dist, | |
| ground_offset, normal_offset, | |
| coll_idx, color, mirror = unpack(def, 1, base_prop_count) | |
| assert(class ~= "Collection") | |
| local class_def = g_Classes[class] | |
| assert(class_def) | |
| if dont_clamp_objects then | |
| fade_dist = false | |
| end | |
| if ignore_ground_offset then | |
| ground_offset = false | |
| end | |
| local new_pos, new_angle, new_axis = GetPrefabObjPos( | |
| dpos, angle, daxis, fade_dist, | |
| prefab_pos, prefab_angle, | |
| ground_offset, normal_offset) | |
| if class_def and new_pos then | |
| local components = 0 | |
| if rmf_flags then | |
| components = components | cofComponentRandomMap | |
| end | |
| local obj = class_def:new(nil, components) | |
| if fadein then | |
| obj:SetOpacity(0) | |
| if fadein ~= -1 then | |
| obj:SetOpacity(100, fadein) | |
| end | |
| end | |
| SetPrefabObjPos(obj, new_pos, new_angle, new_axis, scale, color, mirror) | |
| for i=base_prop_count+1,#def,2 do | |
| PropObjSetProperty(obj, def[i], def[i + 1]) | |
| end | |
| if rmf_flags then | |
| obj:SetRandomMapFlags(rmf_flags) | |
| end | |
| if coll_idx and save_collections then | |
| local placed_idx = remap_col_idx and remap_col_idx[coll_idx] | |
| if not placed_idx then | |
| placed_idx = (last_col_idx or 0) + 1 | |
| local collections = Collections | |
| while collections[placed_idx] do | |
| placed_idx = placed_idx + 1 | |
| end | |
| if placed_idx <= const.GameObjectMaxCollectionIndex then | |
| local col = Collection:new() | |
| col:SetIndex(placed_idx) | |
| col:SetName(string.format("MapGen_%s", placed_idx)) | |
| SetGameFlags(col, gof) | |
| remap_col_idx = table.create_set(remap_col_idx, coll_idx, placed_idx) | |
| else | |
| assert(false, "Too many collections created!") | |
| placed_idx = 0 | |
| end | |
| remap_col_idx[coll_idx] = placed_idx | |
| end | |
| if placed_idx ~= 0 then | |
| obj:SetCollectionIndex(placed_idx) | |
| end | |
| end | |
| SetGameFlags(obj, gof) | |
| objs[#objs + 1] = obj | |
| end | |
| end | |
| for _, obj in ipairs(objs) do | |
| if obj.__ancestors.Object then | |
| obj:PostLoad() | |
| end | |
| end | |
| if IsEditorActive() then | |
| for _, obj in ipairs(objs) do | |
| if obj.__ancestors.EditorObject then | |
| obj:EditorEnter() | |
| end | |
| end | |
| end | |
| if change_height then | |
| local ClearCachedZ = CObject.ClearCachedZ | |
| local IsValidZ = CObject.IsValidZ | |
| for _, obj in ipairs(existing_objs) do | |
| if IsValid(obj) and not IsValidZ(obj) then | |
| ClearCachedZ(obj) | |
| end | |
| end | |
| end | |
| ResumePassEdits("PlacePrefab") | |
| if create_undo then | |
| table.iappend(existing_objs, objs) | |
| XEditorUndo:EndOp(existing_objs) | |
| end | |
| Msg("PrefabPlaced", name, objs) | |
| return nil, objs, inv_bbox | |
| end | |
| ---- | |
| RandomMapFlags = { | |
| { id = "IgnoreHeightOffset", name = "Ignore Height Offset", flag = const.rmfNoGroundOffset, help = "Disregard the original terrain height offset when placing the object" }, | |
| { id = "KeepNormalOffset", name = "Keep Normal Offset", flag = const.rmfNormalOffset, help = "Keep the original terrain normal offset when placing the object" }, | |
| { id = "OptionalPlacement", name = "Optional Placement", flag = const.rmfOptionalPlacement, help = "The object could be removed when placing its prefab" }, | |
| { id = "MeshOverlapCheck", name = "Mesh Overlap Check", flag = const.rmfMeshOverlapCheck, help = "Check mesh overlap ratio to detect prefab out-of-bounds objects. Otherwise only the object's position is checked" }, | |
| { id = "DeleteOnSteepSlope", name = "Delete On Steep Slope", flag = const.rmfDeleteOnSteepSlope, help = "Will be deleted if placed on a too steep slope" }, | |
| } | |
| function GetDefRandomMapFlags(classdef) | |
| local flags = 0 | |
| for _, info in ipairs(RandomMapFlags) do | |
| if classdef[info.id] then | |
| flags = flags | info.flag | |
| end | |
| end | |
| return flags | |
| end | |
| DefineClass.StripRandomMapProps = { | |
| __parents = { "PropertyObject" }, | |
| properties = {}, | |
| } | |
| for _, info in ipairs(RandomMapFlags) do | |
| local id = info.id | |
| local flag = info.flag | |
| CObject[id] = false | |
| local prop = { category = "Random Map", id = id, name = info.name, editor = "bool", help = info.help } | |
| table.insert(CObject.properties, prop) | |
| table.insert(StripCObjectProperties.properties, { id = id } ) | |
| table.insert(StripRandomMapProps.properties, { id = id }) | |
| CObject["Set" .. id] = function(self, set) | |
| local flags = self:GetRandomMapFlags() | |
| local def_flags | |
| if not flags then | |
| flags = GetDefRandomMapFlags(self) | |
| def_flags = flags | |
| end | |
| if set then | |
| flags = flags | flag | |
| else | |
| flags = flags & ~flag | |
| end | |
| if flags == def_flags then | |
| return | |
| end | |
| self:SetRandomMapFlags(flags) | |
| end | |
| CObject["Get" .. id] = function(self) | |
| local flags = self:GetRandomMapFlags() | |
| if not flags then | |
| return self[id] | |
| end | |
| return (flags & flag) ~= 0 | |
| end | |
| end | |
| DefineClass.PrefabSourceInfo = { | |
| __parents = { "Object", "EditorCallbackObject" }, | |
| properties = { | |
| { category = "Random Map", id = "Prefab", name = "Placed From", editor = "text", default = "", read_only = true, developer = true, buttons = {{name = "Goto", func = "GotoPrefabAction"}} }, | |
| }, | |
| } | |
| function PrefabSourceInfo:EditorCallbackGenerate(generator, object_source) | |
| local prefab = object_source[self] | |
| self.Prefab = prefab and PrefabMarkers[prefab] | |
| end | |
| if not Platform.developer then | |
| PrefabSourceInfo.SetPrefab = empty_func | |
| end | |
| AppendClass.FXSource = { | |
| __parents = { "PrefabSourceInfo" }, | |
| } | |