Added interval for each entity. Optimized event regrowth algorithem and structure check.

This commit is contained in:
secXsQuared 2018-01-27 04:23:18 -05:00
parent 165648c40a
commit 4f11d8b07e
5 changed files with 364 additions and 136 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 secXsQuared
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,7 +1,7 @@
name = "World Regrowth Ex"
name = "Advanced World Regrowth"
description = "Advanced world regrowth"
author = "lolo"
version = "0.0.1"
version = "1.0.0"
forumthread = ""

View File

@ -1,11 +1,19 @@
if GLOBAL.STRINGS.NAMES.MIGRATION_PORTAL then
AddPrefabPostInit("world", function(inst)
-- we have caves
AddPrefabPostInit("forest", function(inst)
if inst.ismastersim then
inst:AddComponent("natural_regrowth")
inst:AddComponent("event_regrowth")
end
end)
AddPrefabPostInit("cave", function(inst)
if inst.ismastersim then
inst:AddComponent("natural_regrowth")
inst:AddComponent("event_regrowth")
end
end)
else
-- only overworld
AddPrefabPostInit("world", function(inst)
if inst.ismastersim then
inst:AddComponent("natural_regrowth")
@ -14,18 +22,80 @@ else
end)
end
local natural =
{
--plants
berrybush = 1440,
berrybush2 = 1440,
berrybush_juicy = 1440,
carrot_planted = 240,
evergreen = 30,
deciduoustree = 30,
marsh_tree = 480,
twiggytree = 480,
flower = 240,
flower_evil = 480,
grass = 240,
blue_mushroom = 240,
red_mushroom = 240,
green_mushroom = 240,
reeds = 480,
sapling = 240,
marsh_bush = 480,
cactus = 480,
rock1 = 240,
rock2 = 240,
rock_flintless = 240,
marbletree=1440,
rock_moon = 480,
stalagmite = 240,
stalagmite_tall = 240,
}
local event =
{
houndbone = 960,
pighead = 960,
marblepillar = 1440,
livingtree = 960,
mandrake = 960,
beehive = 480,
wasphive = 960,
houndmound = 1440,
pighouse = 960,
mermhouse = 960,
spiderden = 960,
molehill = 960,
catcoonden = 960,
tentacle = 480,
rabbithole = 480,
fireflies = 480,
knight = 23,
bishop = 9,
rook = 34,
knight_nightmare = 1440,
bishop_nightmare = 1440,
rook_nightmare = 1440,
monkeypods = 1440,
ruins_statue_mage = 960,
ruins_statue_mage_nogem = 960,
ruins_statue_head = 960,
ruins_statue_head_nogem = 960,
rabbithouse = 960
}
AddComponentPostInit("natural_regrowth", function(component)
component:RegisterRegrowth("evergreen", "evergreen")
for prefab, time in pairs(natural) do
component:RegisterRegrowth(prefab, prefab, time)
end
end)
AddComponentPostInit("event_regrowth", function(component)
component:RegisterRegrowth("knight", "knight")
component:RegisterRegrowth("evergreen", "evergreen")
component:RegisterRegrowth("grass", "grass")
component:RegisterRegrowth("rock1", "rock1")
component:RegisterRegrowth("rock2", "rock2")
for prefab, time in pairs(event) do
component:RegisterRegrowth(prefab, prefab, time)
end
component:FinishModConfig()
end)
end)
--"forest" for the overworld

View File

@ -1,25 +1,36 @@
--------------------------------------------------------------------------
--[[ EventRegrowth class definition ]]
-- A modified version of the original regrowthmanager.lua
-- It acts as a standalone regrowth manager and is independent of the 3 existing ones
-- It's unlikely affected by game updates as long as Klei doesn't change the API (they shouldn't)
-- by lolo Jan. 2018
--------------------------------------------------------------------------
return Class(function(self, inst)
assert(TheWorld.ismastersim, "event_regrowth should not exist on client")
assert(inst.ismastersim, "event_regrowth should not exist on client")
require "map/terrain"
--------------------------------------------------------------------------
--[[ Constants ]]
--------------------------------------------------------------------------
local DEBUG = true
local DEBUG_TELE = true
local RETRY_PER_PREFAB = 10 -- retry 5 times for each prefab
local UPDATE_PERIOD = 3 -- less likely to update on the same frame as others
local DEBUG_TELE = false
local UPDATE_PERIOD = 11
local BASE_RADIUS = 20
local EXCLUDE_RADIUS = 2
local JITTER_RADIUS = 10
local MIN_PLAYER_DISTANCE = 40 -- this is our "outer" sleep radius
local EXCLUDE_RADIUS = 3
local JITTER_RADIUS = 6
local TOTAL_RADIUS = 1000
local MIN_PLAYER_DISTANCE = 40
local THREADS_PER_BATCH = 5 -- since we retry a lot, we reduce the # of threads to guarantee performance
local THREADS_PER_BATCH_HOOK = 5
local REGROW_STATUS = {
SUCCESS = 0,
FAILED = 1,
CACHE = 2,
}
--------------------------------------------------------------------------
--[[ Member variables ]]
@ -43,83 +54,145 @@ return Class(function(self, inst)
end
local position = ent:GetPosition()
table.insert(entity_list[ent.prefab], position)
table.insert(entity_list[ent.prefab], {position = position, interval = regrowth_table[ent.prefab].interval})
ent:RemoveEventCallback("onremove", EntityDeathEventHandler, nil)
if DEBUG then
print("[EventRegrowth] Entity of type ", ent.prefab, " was removed at ", position)
print("[EventRegrowth] ", ent.prefab, " was removed at ", position)
end
end
local function TestForRegrow(x, y, z, tile)
if TheWorld.Map:GetTileAtPoint(x, y, z) ~= tile then
local ents = TheSim:FindEntities(x,y,z, BASE_RADIUS, nil, nil, { "structure", "wall" })
if #ents > 0 then
-- No regrowth around players and their bases
return REGROW_STATUS.FAILED
end
if inst.Map:GetTileAtPoint(x, y, z) ~= tile then
-- keep things in their biome (more or less)
return false
return REGROW_STATUS.CACHE
end
local ents = TheSim:FindEntities(x,y,z, EXCLUDE_RADIUS)
if #ents > 0 then
-- Too dense
return false
return REGROW_STATUS.CACHE
end
if IsAnyPlayerInRange(x,y,z, MIN_PLAYER_DISTANCE, nil) then
return REGROW_STATUS.CACHE
end
return REGROW_STATUS.SUCCESS
end
-- duplicate of canregrow in natural regrowth
local function CanRegrow(x, y, z, prefab)
if IsAnyPlayerInRange(x,y,z, MIN_PLAYER_DISTANCE, nil) then
return false
end
local ents = TheSim:FindEntities(x,y,z, EXCLUDE_RADIUS)
if #ents > 0 then
-- Too dense
return false
end
local ents = TheSim:FindEntities(x,y,z, BASE_RADIUS, nil, nil, { "structure", "wall" })
if #ents > 0 then
-- No regrowth around players and their bases
-- Don't spawn inside bases
return false
end
if not (inst.Map:CanPlantAtPoint(x, y, z) and
inst.Map:CanPlacePrefabFilteredAtPoint(x, y, z, prefab))
or (RoadManager ~= nil and RoadManager:IsOnRoad(x, 0, z)) then
-- Not ground we can grow on
return false
end
return true
end
local function TryRegrowth(prefab, product, position)
local x = position.x
local y = position.y
local z = position.z
local orig_tile = TheWorld.Map:GetTileAtPoint(x,y,z)
local function GetRandomLocation(x, y, z, radius)
local theta = math.random() * 2 * PI
local radius = math.random() * JITTER_RADIUS
local radius = math.random() * radius
local x = x + radius * math.cos(theta)
local z = z - radius * math.sin(theta)
if TestForRegrow(x,y,z, orig_tile) then
local instance = SpawnPrefab(product)
if instance ~= nil then
instance.Transform:SetPosition(x,y,z)
instance:ListenForEvent("onremove", EntityDeathEventHandler, nil)
if DEBUG then
print("[EventRegrowth] Spawned a ",product," for prefab ",prefab," at ", "(", x,0,z, ")")
end
if DEBUG_TELE then
c_teleport(x,0,z)
end
end
return true
else
return false
end
return x,y,z
end
local function HookAllEntities()
local function TryRegrowth(prefab, product, position)
local x,y,z = GetRandomLocation(position.x,position.y,position.z,JITTER_RADIUS)
local orig_tile = inst.Map:GetTileAtPoint(x,y,z)
local status = TestForRegrow(x,y,z, orig_tile)
if status == REGROW_STATUS.CACHE then
if DEBUG then
print("[EventRegrowth] Cached a ",product," for prefab ",prefab," at ", x, ",", y,",",z)
end
return false
end
if status == REGROW_STATUS.FAILED then
-- for the failed case, we want to try spawning at a random location
x,y,z = GetRandomLocation(position.x,position.y,position.z,TOTAL_RADIUS)
if not CanRegrow(x,y,z, product) then
-- if cannot regrow, return CACHE status
if DEBUG then
print("[EventRegrowth] Failed to spawn a ",product," for prefab ",prefab," at ", x, ",", y,",",z)
end
return false
end
end
local instance = SpawnPrefab(product)
if instance ~= nil then
instance.Transform:SetPosition(x,y,z)
instance:ListenForEvent("onremove", EntityDeathEventHandler, nil)
if DEBUG then
print("[EventRegrowth] Spawned a ",product," for prefab ",prefab," at ", x, ",", y,",",z)
end
if DEBUG_TELE then
c_teleport(x,0,z)
end
end
return true
end
local function HookEntities(prefab)
while next(Ents) == nil do
end
local count = 0
for k, v in pairs(Ents) do
if regrowth_table[v.prefab] ~= nil then
if v.prefab == prefab then
v:RemoveEventCallback("onremove", EntityDeathEventHandler, nil)
v:ListenForEvent("onremove", EntityDeathEventHandler, nil)
count = count + 1
end
end
if DEBUG then
print("[EventRegrowth] Hooked ", count, " entities.")
print("[EventRegrowth] Hooked ", count, " ",prefab)
end
end
local function HookAllEntities(ents)
local count = 0
local delay = 0
for prefab in pairs(ents) do
inst:DoTaskInTime(delay, function() HookEntities(prefab) end)
count = count + 1
if math.fmod(count, THREADS_PER_BATCH_HOOK) == 0 then
delay = delay + 1
end
end
end
@ -130,11 +203,15 @@ return Class(function(self, inst)
regrowth_table_populated_by_mod = true
end
function self:RegisterRegrowth(prefab, product)
function self:RegisterRegrowth(prefab, product, interval)
if regrowth_table[prefab] == nil then
-- avoid duplicate registration
regrowth_table[prefab] = product
HookAllEntities()
regrowth_table[prefab] =
{
product = product,
interval = interval
}
HookEntities(prefab)
end
if DEBUG then
@ -147,12 +224,23 @@ return Class(function(self, inst)
--------------------------------------------------------------------------
inst:DoPeriodicTask(UPDATE_PERIOD, function() self:LongUpdate(UPDATE_PERIOD) end)
inst:ListenForEvent("ms_cyclecomplete", function() HookAllEntities(regrowth_table) end) -- every ~ 1 day we rehook every entities
inst:DoTaskInTime(0, function() HookAllEntities(regrowth_table) end)
inst:DoPeriodicTask(99, HookAllEntities, 0)
--------------------------------------------------------------------------
--[[ Update ]]
--------------------------------------------------------------------------
local function RegrowPrefabTask(prefab)
for i = #entity_list[prefab],1,-1 do
local success = TryRegrowth(prefab, regrowth_table[prefab].product, entity_list[prefab][i].position)
if success then
-- remove from the list if it's success or failed
table.remove(entity_list[prefab], i)
end
end
end
function self:LongUpdate(dt)
if not regrowth_table_populated_by_mod then
-- do nothing if the table is not fully initialized
@ -160,40 +248,39 @@ return Class(function(self, inst)
return
end
local count = 0
local delay = 0
for prefab in pairs(entity_list) do
if entity_list[prefab] == nil or #entity_list[prefab] == 0 then
-- only do meaningful work
else
if DEBUG then
print("[EventRegrowth] Regrowing ", prefab, "...")
end
if regrowth_table[prefab] == nil then
-- if we don't have it registered, discard
entity_list[prefab] = nil
if DEBUG then
print("[EventRegrowth] Discarded")
end
else
for i = #entity_list[prefab],1,-1 do
for i = 1, #entity_list[prefab] do
-- decrease the interval
if entity_list[prefab][i].interval > UPDATE_PERIOD then
entity_list[prefab][i].interval = entity_list[prefab][i].interval - UPDATE_PERIOD
else
-- else set to 0 and regen
entity_list[prefab][i].interval = 0
end
if DEBUG then
print("[EventRegrowth] Spawning at location", entity_list[prefab][i])
print("[EventRegrowth]", prefab, " at ", entity_list[prefab][i].position, " has interval ", entity_list[prefab][i].interval )
end
local attempts = 0
while attempts < RETRY_PER_PREFAB do
local success = TryRegrowth(prefab, regrowth_table[prefab], entity_list[prefab][i])
attempts = attempts + 1
if success then
print("[EventRegrowth] Succeeded after ", attempts, " attempts.")
-- we respawned this guy, remove from the list
table.remove(entity_list[prefab], i)
break
if entity_list[prefab][i].interval == 0 then
-- different threads
inst:DoTaskInTime(delay, function() RegrowPrefabTask(prefab) end)
-- try not to flood the server with threads
count = count + 1
if math.fmod( count,THREADS_PER_BATCH ) == 0 then
delay = delay + 1
end
end
if DEBUG and attempts == RETRY_PER_PREFAB then
print("[EventRegrowth] Failed after ", attempts, " attempts.")
end
end
end
end
@ -205,11 +292,27 @@ return Class(function(self, inst)
--------------------------------------------------------------------------
function self:OnSave()
local data = {
entities = {}
}
for prefab in pairs(entity_list) do
data.entities[prefab] = {}
for i = 1, #entity_list[prefab] do
table.insert(data.entities[prefab], {interval = entity_list[prefab][i].interval, position = entity_list[prefab][i].position})
end
end
return data
end
function self:OnLoad(data)
for prefab in pairs(data.entities) do
if entity_list[prefab] == nil then
entity_list[prefab] = {}
end
for i = 1, #data.entities[prefab] do
table.insert(entity_list[prefab], {interval = data.entities[prefab][i].interval, position = data.entities[prefab][i].position})
end
end
end
--------------------------------------------------------------------------

View File

@ -3,27 +3,25 @@
-- A modified version of the original desolationspawner.lua
-- It acts as a standalone regrowth manager and is independent of the 3 existing ones
-- It's unlikely affected by game updates as long as Klei doesn't change the API (they shouldn't)
-- Klei has copyright over existing code used in this file.
-- by lolo Jan. 2018.
-- by lolo Jan. 2018
--------------------------------------------------------------------------
return Class(function(self, inst)
assert(TheWorld.ismastersim, "natrual_regrowth should not exist on client")
assert(inst.ismastersim, "natrual_regrowth should not exist on client")
require "map/terrain"
--------------------------------------------------------------------------
--[[ Constants ]]
--------------------------------------------------------------------------
local RETRY_PER_PREFAB = 10 -- retry 5 times for each prefab
local DEBUG = false
local DEBUG_TELE = false
local UPDATE_PERIOD = 31 -- less likely to update on the same frame as others
local UPDATE_PERIOD = 9
local BASE_RADIUS = 20
local EXCLUDE_RADIUS = 2
local MIN_PLAYER_DISTANCE = 40 -- this is our "outer" sleep radius
local EXCLUDE_RADIUS = 3
local MIN_PLAYER_DISTANCE = 40
local THREADS_PER_BATCH = 5
--------------------------------------------------------------------------
--[[ Member variables ]]
--------------------------------------------------------------------------
@ -34,6 +32,7 @@ return Class(function(self, inst)
--Private
local regrowth_table = {}
local area_data = {}
local intervals = {}
--------------------------------------------------------------------------
--[[ Private member functions ]]
@ -57,8 +56,8 @@ return Class(function(self, inst)
return false
end
if not (TheWorld.Map:CanPlantAtPoint(x, y, z) and
TheWorld.Map:CanPlacePrefabFilteredAtPoint(x, y, z, prefab))
if not (inst.Map:CanPlantAtPoint(x, y, z) and
inst.Map:CanPlacePrefabFilteredAtPoint(x, y, z, prefab))
or (RoadManager ~= nil and RoadManager:IsOnRoad(x, 0, z)) then
-- Not ground we can grow on
return false
@ -67,13 +66,13 @@ return Class(function(self, inst)
end
local function TryRegrowth(area, prefab, product)
if TheWorld.topology.nodes[area] == nil then
if inst.topology.nodes[area] == nil then
return false
end
local points_x, points_y = TheWorld.Map:GetRandomPointsForSite(TheWorld.topology.nodes[area].x, TheWorld.topology.nodes[area].y, TheWorld.topology.nodes[area].poly, 1)
local points_x, points_y = inst.Map:GetRandomPointsForSite(inst.topology.nodes[area].x, inst.topology.nodes[area].y, inst.topology.nodes[area].poly, 1)
if #points_x < 1 or #points_y < 1 then
return
return false
end
local x = points_x[1]
local z = points_y[1]
@ -95,20 +94,32 @@ return Class(function(self, inst)
return true
else
if DEBUG then
print("[NaturalRegrowth] Failed to spawn a ",product," for prefab ",prefab," at ", "(", x,0,z, ")", " in ", area)
end
return false
end
end
local function PrintDensities()
for area, densities in pairs(inst.generated.densities) do
for k,v in pairs(densities) do
print(area, k, v)
end
end
end
local function PopulateAreaData(prefab)
if TheWorld.generated == nil then
if inst.generated == nil then
-- Still starting up, not ready yet.
return
end
for area, densities in pairs(TheWorld.generated.densities) do
-- PrintDensities()
for area, densities in pairs(inst.generated.densities) do
if densities[prefab] ~= nil then
for id, v in ipairs(TheWorld.topology.ids) do
for id, v in ipairs(inst.topology.ids) do
if v == area then
if area_data[prefab] == nil then
area_data[prefab] = {}
@ -120,11 +131,15 @@ return Class(function(self, inst)
end
end
end
if DEBUG then
print("[NaturalRegrowth] Populated ", area_data[prefab] == nil and 0 or #area_data[prefab], " areas for ", prefab)
end
end
local function PopulateAllAreaData()
-- This has to be run after 1 frame from startup
for prefab, _ in pairs(regrowth_table) do
for prefab in pairs(regrowth_table) do
PopulateAreaData(prefab)
end
end
@ -133,11 +148,16 @@ return Class(function(self, inst)
--[[ Public member functions ]]
--------------------------------------------------------------------------
function self:RegisterRegrowth(prefab, product)
function self:RegisterRegrowth(prefab, product, interval)
if DEBUG then
print("Registered ", product, " for prefab " ,prefab )
print("[NaturalRegrowth] Registered ", product, " for prefab " ,prefab )
end
regrowth_table[prefab] = product
regrowth_table[prefab] = {product = product, interval = interval}
if intervals[prefab] == nil then
intervals[prefab] = interval
end
PopulateAreaData(prefab)
end
@ -152,39 +172,44 @@ return Class(function(self, inst)
--------------------------------------------------------------------------
--[[ Update ]]
--------------------------------------------------------------------------
local function RegrowPrefabTask(areas, prefab)
local rand = math.random(1, #areas)
local success = TryRegrowth(areas[rand], prefab, regrowth_table[prefab].product)
if success then
-- success, reset the timer
intervals[prefab] = regrowth_table[prefab] == nil and nil or regrowth_table[prefab].interval
end
end
function self:LongUpdate(dt)
local count = 0
local delay = 0
for prefab in pairs(area_data) do
if DEBUG then
print("[NaturalRegrowth] Regrowing ", prefab, "...")
end
local areas = area_data[prefab]
if regrowth_table[prefab] == nil then
if DEBUG then
print("[NaturalRegrowth] Discarded")
end
area_data[prefab] = nil
intervals[prefab] = nil
else
local rand = math.random(1, #areas)
local attempts = 0
while attempts < RETRY_PER_PREFAB do
local success = TryRegrowth(areas[rand], prefab, regrowth_table[prefab])
attempts = attempts + 1
if success then
if DEBUG then
print("[NaturalRegrowth] Succeeded after ", attempts, " attempts.")
end
break
end
if intervals[prefab] > UPDATE_PERIOD then
intervals[prefab] = intervals[prefab] - UPDATE_PERIOD
else
intervals[prefab] = 0
end
if DEBUG then
print("[NaturalRegrowth]", prefab, " has interval ", intervals[prefab])
end
if DEBUG and attempts == RETRY_PER_PREFAB then
print("[NaturalRegrowth] Failed after ", attempts, " attempts.")
if intervals[prefab] == 0 then
-- use multiple threads? In the future a threadpool maybe?
inst:DoTaskInTime(delay, function() RegrowPrefabTask(areas,prefab) end)
-- try not to flood the server with threads
count = count + 1
if math.fmod( count,THREADS_PER_BATCH ) == 0 then
delay = delay + 1
end
end
end
end
@ -196,25 +221,34 @@ return Class(function(self, inst)
function self:OnSave()
local data = {
areas = {}
areas = {},
intervals = {}
}
for prefab in pairs(area_data) do
data.areas[prefab] = {}
for area in pairs(area_data[prefab]) do
table.insert(data.areas[prefab], area)
for i = 1, #area_data[prefab] do
table.insert(data.areas[prefab], area_data[prefab][i])
end
end
for prefab, interval in pairs(intervals) do
data.intervals[prefab] = interval
end
return data
end
function self:OnLoad(data)
for prefab in pairs(data.areas) do
for area in pairs(data.areas) do
if area_data[prefab] == nil then
area_data[prefab] = {}
end
table.insert(area_data[prefab], area)
if area_data[prefab] == nil then
area_data[prefab] = {}
end
for i = 1, #data.areas[prefab] do
table.insert(area_data[prefab], data.areas[prefab][i])
end
end
for prefab, interval in pairs(data.intervals) do
intervals[prefab] = interval
end
end