|
local function GetPrefabItems(self) |
|
local items = {} |
|
local PrefabMarkers = PrefabMarkers |
|
for _, prefab in ipairs(self:FilterPrefabs()) do |
|
items[#items + 1] = PrefabMarkers[prefab] |
|
end |
|
table.sort(items) |
|
table.insert(items, 1, "") |
|
return items |
|
end |
|
|
|
DefineClass.PlacePrefabLogic = { |
|
__parents = { "PropertyObject" }, |
|
properties = { |
|
{ category = "Prefab", id = "FixedPrefab", name = "Fixed Prefabs", editor = "string_list", default = false, items = GetPrefabItems, no_validate = true, buttons = {{name = "Test", func = "TestPlacePrefab"}}}, |
|
{ category = "Prefab", id = "PrefabPOIType", name = "Prefab POI Type", editor = "preset_id", default = "", preset_class = "PrefabPOI" }, |
|
{ category = "Prefab", id = "PrefabType", name = "Prefab Type", editor = "preset_id", default = "", preset_class = "PrefabType" }, |
|
{ category = "Prefab", id = "PrefabTagsAny", name = "Prefab Tags Any", editor = "set", default = empty_table, items = function() return PrefabTagsCombo() end, three_state = true }, |
|
{ category = "Prefab", id = "PrefabTagsAll", name = "Prefab Tags All", editor = "set", default = empty_table, items = function() return PrefabTagsCombo() end, three_state = true }, |
|
{ category = "Prefab", id = "MaxPrefabRadius", name = "Max Allowed Radius", editor = "number", default = 0, scale = "m" }, |
|
{ category = "Prefab", id = "FixAtCenter", name = "Fix At Center", editor = "bool", default = true, help = "Allow the prefab to be spawned anywhere inside the max radius" }, |
|
{ category = "Prefab", id = "RandAngle", name = "Rand Angle", editor = "number", default = 0, scale = "deg" }, |
|
{ category = "Prefab", id = "PlacedName", name = "Placed Name", editor = "text", default = "", read_only = true, dont_save = true, buttons = {{name = "Goto", func = "GotoPrefabAction"}} }, |
|
{ category = "Prefab", id = "PlaceError", name = "Place Error", editor = "text", default = "", read_only = true, dont_save = true }, |
|
{ category = "Prefab", id = "PrefabCount", name = "Prefab Count", editor = "number", default = 0, read_only = true, dont_save = true }, |
|
}, |
|
reserved_locations = false, |
|
} |
|
|
|
function PlacePrefabLogic:SetFixedPrefab(prefab) |
|
if type(prefab) == "string" then |
|
prefab = { prefab } |
|
end |
|
self.FixedPrefab = prefab |
|
end |
|
|
|
function PlacePrefabLogic:FilterPrefabs(params, all_prefabs) |
|
all_prefabs = all_prefabs or PrefabMarkers |
|
local prefabs = {} |
|
local poi_type = params and params.poi_type or self.PrefabPOIType or "" |
|
local ptype = params and params.prefab_type or self.PrefabType or "" |
|
local max_radius = params and params.max_radius or self.MaxPrefabRadius or 0 |
|
local tags_any = params and params.tags_any or self.PrefabTagsAny |
|
local tags_all = params and params.tags_all or self.PrefabTagsAll |
|
local type_tile = const.TypeTileSize |
|
for _, prefab in ipairs(all_prefabs) do |
|
if (poi_type == "" or prefab.poi_type == poi_type) |
|
and (ptype == "" or prefab.type == "" or prefab.type == ptype) |
|
and (max_radius == 0 or prefab.max_radius * type_tile <= max_radius) |
|
and MatchThreeStateSet(prefab.tags, tags_any, tags_all) |
|
then |
|
prefabs[#prefabs + 1] = prefab |
|
end |
|
end |
|
return prefabs |
|
end |
|
|
|
function PlacePrefabLogic:GetPrefabs(params) |
|
local fixed_prefabs = params and params.name or self.FixedPrefab |
|
if fixed_prefabs then |
|
local prefabs = {} |
|
if type(fixed_prefabs) == "string" then |
|
fixed_prefabs = { fixed_prefabs } |
|
end |
|
for _, name in ipairs(fixed_prefabs) do |
|
local prefab = PrefabMarkers[name] |
|
if not prefab then |
|
StoreErrorSource(self, "No such prefab:", name) |
|
else |
|
prefabs[#prefabs + 1] = prefab |
|
end |
|
end |
|
if params and params.filter_fixed then |
|
local tags_any = params and params.tags_any or self.PrefabTagsAny |
|
local tags_all = params and params.tags_all or self.PrefabTagsAll |
|
for i = #prefabs,1,-1 do |
|
if not MatchThreeStateSet(prefabs[i].tags, tags_any, tags_all) then |
|
table.remove_rotate(prefabs, i) |
|
end |
|
end |
|
end |
|
if #prefabs > 0 then |
|
return prefabs |
|
end |
|
end |
|
return self:FilterPrefabs(params) |
|
end |
|
|
|
function PlacePrefabLogic:GetError() |
|
if mapdata.IsPrefabMap and self:GetPrefabCount() == 0 then |
|
return "No matching prefabs found!" |
|
end |
|
end |
|
|
|
function PlacePrefabLogic:GetPrefabCount(params) |
|
return #self:GetPrefabs(params) |
|
end |
|
|
|
function PlacePrefabLogic:ReserveLocation(pos, radius) |
|
self.reserved_locations = table.create_add(self.reserved_locations, {pos, radius}) |
|
end |
|
|
|
function PlacePrefabLogic:GetReservedRatio() |
|
local max_radius = self.MaxPrefabRadius |
|
if max_radius <= 0 then |
|
return 100 |
|
end |
|
local radius_sum2 = 0 |
|
for _, info in ipairs(self.reserved_locations) do |
|
local radius = info[2] |
|
radius_sum2 = radius * radius |
|
end |
|
return 100 * sqrt(radius_sum2) / max_radius |
|
end |
|
|
|
function PlacePrefabLogic:CheckReservedLocations(pos, radius) |
|
|
|
for _, info in ipairs(self.reserved_locations) do |
|
if IsCloser2D(pos, info[1], radius + info[2]) then |
|
|
|
return |
|
end |
|
end |
|
|
|
return true |
|
end |
|
|
|
function PlacePrefabLogic:GetPrefabLoc(seed, params) |
|
seed = seed or InteractionRand(nil, "PlacePrefab") |
|
local name, pos, angle, prefab, idx |
|
local prefabs = self:GetPrefabs(params) |
|
local retry |
|
while true do |
|
local idx |
|
if #prefabs > 1 then |
|
prefab, idx, seed = table.weighted_rand(prefabs, "weight", seed) |
|
else |
|
prefab = prefabs[1] |
|
end |
|
assert(prefab) |
|
if not prefab then |
|
return |
|
end |
|
pos = params and params.pos |
|
if not pos then |
|
pos = self:GetVisualPos() |
|
if not self.FixAtCenter then |
|
local reserved_radius |
|
if params and params.avoid_reserved_locations and self.reserved_locations then |
|
reserved_radius = (prefab.min_radius + prefab.max_radius) * const.TypeTileSize / 2 |
|
end |
|
local radius = prefab.max_radius * const.TypeTileSize |
|
local free_dist = self.MaxPrefabRadius - radius |
|
if free_dist > 0 then |
|
local center = pos |
|
pos = false |
|
local retries = params and params.avoid_reserved_retries or 16 |
|
for i=1,retries do |
|
local ra, rr |
|
ra, seed = BraidRandom(seed, 360*60) |
|
rr, seed = BraidRandom(seed, free_dist) |
|
local pos_i = RotateRadius(rr, ra, center) |
|
if not reserved_radius or self:CheckReservedLocations(pos_i, reserved_radius) then |
|
pos = pos_i |
|
break |
|
end |
|
end |
|
elseif reserved_radius and not self:CheckReservedLocations(pos, reserved_radius) then |
|
pos = false |
|
end |
|
end |
|
end |
|
if pos then |
|
name = PrefabMarkers[prefab] |
|
angle = params and params.angle |
|
if not angle then |
|
angle = self:GetAngle() |
|
local rand_angle = self.RandAngle |
|
if rand_angle > 0 then |
|
local desired_angle = params and params.desired_angle |
|
if desired_angle then |
|
local angle_diff = AngleDiff(desired_angle, angle) |
|
if abs(angle_diff) <= rand_angle then |
|
angle = desired_angle |
|
else |
|
local min_angle, max_angle = angle - rand_angle, angle + rand_angle |
|
if abs(AngleDiff(desired_angle, min_angle)) < abs(AngleDiff(desired_angle, max_angle)) then |
|
angle = min_angle |
|
else |
|
angle = max_angle |
|
end |
|
end |
|
else |
|
local da |
|
da, seed = BraidRandom(seed, -rand_angle, rand_angle) |
|
angle = angle + da |
|
end |
|
end |
|
end |
|
return name, pos, angle, prefab, seed |
|
end |
|
if #prefabs == 1 then |
|
return |
|
end |
|
table.remove_rotate(prefabs, idx) |
|
end |
|
end |
|
|
|
function PlacePrefabLogic:PlacePrefab(seed, params) |
|
local success, err, objs, inv_bbox |
|
local name, pos, angle, prefab, seed = self:GetPrefabLoc(seed, params) |
|
if not name then |
|
err = "No matching prefabs found!" |
|
else |
|
success, err, objs, inv_bbox = procall(PlacePrefab, name, pos, angle, seed, params) |
|
end |
|
self.PlaceError = err |
|
self.PlacedName = name |
|
ObjModified(self) |
|
return err, objs, pos, prefab, name, inv_bbox |
|
end |
|
|
|
function PlacePrefabLogic:EditorCallbackGenerate(generator, object_source, placed_objects, prefab_list) |
|
local mark = placed_objects[self] |
|
local info = mark and prefab_list[mark] |
|
local ptype = info and info[4] |
|
if ptype then |
|
self.PrefabType = ptype |
|
end |
|
end |
|
|
|
|
|
|
|
DefineClass.PlacePrefabMarker = { |
|
__parents = { "RadiusMarker", "PlacePrefabLogic", "PrefabSourceInfo" }, |
|
editor_text_color = RGB(50, 50, 100), |
|
editor_color = RGB(150, 150, 0), |
|
} |
|
|
|
function PlacePrefabMarker:GetMeshRadius() |
|
local max_radius = self.MaxPrefabRadius |
|
for _, prefab in ipairs(self:GetPrefabs()) do |
|
max_radius = Max(max_radius, prefab.max_radius) |
|
end |
|
return max_radius |
|
end |
|
|
|
function PlacePrefabMarker:OnEditorSetProperty(prop_id, old_value, ged) |
|
local meta = self:GetPropertyMetadata(prop_id) |
|
if meta and meta.category == "Prefab" then |
|
self:UpdateMeshRadius() |
|
end |
|
end |
|
|
|
function PlacePrefabMarker:TestPlacePrefab() |
|
local err, objs = self:PlacePrefab(AsyncRand(), { |
|
create_undo = true, |
|
}) |
|
if err then |
|
print(err) |
|
end |
|
end |
|
|