2018-01-23 10:13:21 +00:00
--------------------------------------------------------------------------
--[[ EventRegrowth class definition ]]
2019-10-27 08:52:14 +00:00
-- A modified and more feature-rich version of the original regrowthmanager.lua
2018-01-27 09:23:18 +00:00
-- It acts as a standalone regrowth manager and is independent of the 3 existing ones
2019-10-27 08:52:14 +00:00
-- It's unlikely affected by game updates as long as Klei doesn't break the API (they shouldn't)
-- quackerd
2018-01-23 10:13:21 +00:00
--------------------------------------------------------------------------
return Class ( function ( self , inst )
2018-01-27 09:23:18 +00:00
2019-10-27 08:52:14 +00:00
assert ( inst.ismastersim , " extra_regrowth should not exist on client " )
2018-01-23 10:13:21 +00:00
require " map/terrain "
2019-10-27 08:52:14 +00:00
require " ocean_util "
2018-01-23 10:13:21 +00:00
--------------------------------------------------------------------------
--[[ Constants ]]
--------------------------------------------------------------------------
2018-01-28 19:11:49 +00:00
local DEBUG = false
2018-01-27 09:23:18 +00:00
local DEBUG_TELE = false
2019-10-27 08:52:14 +00:00
local UPDATE_PERIOD = 9 -- 9 seconds per tick
local JITTER_RADIUS = 1 -- random point within this radius
local MAX_RADIUS = 1000 -- cap the max radius
local INC_RADIUS = 3 -- increase this amount after we fail
local BASE_RADIUS = 15 -- don't spawn near player's base
local EXCLUDE_RADIUS = 2 -- no other entities in this radius within the spawn point
local MIN_PLAYER_DISTANCE = 30 -- minimum distance of player to spawn entities
local EXCLUDE_TAGS =
{
" statue " , -- marble stuff on the ground, ancient statues
" hive " , --spiderden, wasphive, beehive
} -- these aren't considered structures
local EXCLUDE_PREFABS =
{
catcoonden = 1 ,
ancient_altar = 1 ,
ancient_altar_broken = 1 ,
houndmound = 1 ,
mermhouse = 1 ,
pigtorch = 1 ,
mermhead = 1 ,
pighead = 1 ,
pandoraschest = 1 ,
minotaurchest = 1 ,
pighouse = 1 ,
rabbithouse = 1 ,
chessjunk1 = 1 ,
chessjunk2 = 1 ,
chessjunk3 = 1 ,
wall_ruins = 1 ,
} -- these aren't considered structures
local REGROW_STATUS =
{
SUCCESS = 0 ,
STRUCT = 1 ,
CACHE = 2 ,
PLAYER = 3 ,
DENSITY = 4 ,
TILE = 5 ,
ROAD = 6 ,
OCEAN = 7
}
local CACHE_RETRY =
{
[ REGROW_STATUS.PLAYER ] = true ,
} -- we don't increase the radius for these reasons
2018-01-23 10:13:21 +00:00
--------------------------------------------------------------------------
--[[ Member variables ]]
--------------------------------------------------------------------------
--Public
self.inst = inst
--Private
2019-10-27 08:52:14 +00:00
local ready = false
2018-01-23 10:13:21 +00:00
local regrowth_table = { }
local entity_list = { }
--------------------------------------------------------------------------
--[[ Private member functions ]]
--------------------------------------------------------------------------
2019-10-27 08:52:14 +00:00
local function TestStructures ( x , y , z , radius )
local ents = TheSim : FindEntities ( x , y , z , BASE_RADIUS , nil , EXCLUDE_TAGS , { " structure " , " wall " } )
for i , v in ipairs ( ents ) do
if EXCLUDE_PREFABS [ v.prefab ] == nil then
-- if we cannot find it from the exclude table, then it is a structure and we failed the test
return false
end
end
return true
end
local function CanPlaceAtPoint ( x , y , z )
local tile = TheWorld.Map : GetTileAtPoint ( x , y , z )
return tile ~= GROUND.IMPASSABLE and
tile ~= GROUND.INVALID and
not GROUND_FLOORING [ tile ]
end
local function TestPlayers ( x , y , z , radius )
return not IsAnyPlayerInRange ( x , y , z , MIN_PLAYER_DISTANCE , nil )
end
local function TestEntities ( x , y , z , radius )
local ents = TheSim : FindEntities ( x , y , z , EXCLUDE_RADIUS )
return not ( # ents > 0 )
end
local function TestRegrowth ( x , y , z , prefab , tile )
local cur_tile = TheWorld.Map : GetTileAtPoint ( x , y , z )
if not TestPlayers ( x , y , z , MIN_PLAYER_DISTANCE ) then
return REGROW_STATUS.PLAYER
end
if not TestStructures ( x , y , z , BASE_RADIUS ) then
-- No regrowth around players and their bases
return REGROW_STATUS.STRUCT
end
if not TestEntities ( x , y , z , EXCLUDE_RADIUS ) then
-- Too dense
return REGROW_STATUS.DENSITY
end
if ( RoadManager ~= nil ) and ( RoadManager : IsOnRoad ( x , 0 , z ) ) then
return REGROW_STATUS.ROAD
end
-- hack to get away with it for now
if IsOceanTile ( cur_tile ) then
return REGROW_STATUS.OCEAN
end
if ( CanPlaceAtPoint ( x , y , z ) and TheWorld.Map : CanPlacePrefabFilteredAtPoint ( x , y , z , prefab ) ) or ( ( tile ~= nil ) and ( cur_tile == tile ) ) then
return REGROW_STATUS.SUCCESS
end
return REGROW_STATUS.TILE
end
local function GetPosStr ( pos )
return " ( " .. pos.x .. " , " .. pos.y .. " , " .. pos.z .. " ) "
end
local function GetCoordStr ( x , y , z )
return " ( " .. x .. " , " .. y .. " , " .. z .. " ) "
end
local function GetRStatusStr ( status )
for k , v in pairs ( REGROW_STATUS ) do
if v == status then
return k
end
end
return nil
end
2018-01-23 10:13:21 +00:00
local function EntityDeathEventHandler ( ent )
if entity_list [ ent.prefab ] == nil then
entity_list [ ent.prefab ] = { }
end
local position = ent : GetPosition ( )
2018-02-01 11:33:38 +00:00
entity_list [ ent.prefab ] [ # entity_list [ ent.prefab ] + 1 ] =
{
position = position ,
interval = regrowth_table [ ent.prefab ] . interval ,
remove = false ,
retry = 0
}
2018-01-23 10:13:21 +00:00
ent : RemoveEventCallback ( " onremove " , EntityDeathEventHandler , nil )
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] " .. ent.prefab .. " was removed at " .. GetPosStr ( position ) .. " Tile: " .. TheWorld.Map : GetTileAtPoint ( position.x , position.y , position.z ) )
2018-01-23 10:13:21 +00:00
end
end
2018-02-01 11:33:38 +00:00
local function GetRandomLocation ( x , y , z , r )
2018-01-23 10:13:21 +00:00
local theta = math.random ( ) * 2 * PI
2018-02-01 11:33:38 +00:00
local radius = math.random ( ) * r
2018-01-23 10:13:21 +00:00
local x = x + radius * math.cos ( theta )
local z = z - radius * math.sin ( theta )
2018-01-27 09:23:18 +00:00
return x , y , z
end
2018-01-23 10:13:21 +00:00
2018-02-01 11:33:38 +00:00
local function TryRegrowth ( prefab , product , position , rand_radius )
local x , y , z = GetRandomLocation ( position.x , position.y , position.z , rand_radius )
2018-02-03 02:53:04 +00:00
local orig_tile = inst.Map : GetTileAtPoint ( position.x , position.y , position.z )
local status = TestRegrowth ( x , y , z , prefab , orig_tile )
2019-10-27 08:52:14 +00:00
if CACHE_RETRY [ status ] ~= nil then
2018-01-27 09:23:18 +00:00
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Cached a " .. product .. " at " .. GetCoordStr ( x , y , z ) .. " for " .. prefab .. " at " .. GetPosStr ( position ) .. " with radius " .. rand_radius .. " due to " .. GetRStatusStr ( status ) )
2018-01-27 09:23:18 +00:00
end
2018-02-03 02:53:04 +00:00
return status
2018-01-27 09:23:18 +00:00
end
2018-02-03 02:53:04 +00:00
if status ~= REGROW_STATUS.SUCCESS then
2018-02-01 11:33:38 +00:00
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Failed to spawn a " .. product .. " at " .. GetCoordStr ( x , y , z ) .. " for " .. prefab .. " at " .. GetPosStr ( position ) .. " with radius " .. rand_radius .. " due to " .. GetRStatusStr ( status ) )
2018-01-27 09:23:18 +00:00
end
2018-02-03 02:53:04 +00:00
return status
2018-01-27 09:23:18 +00:00
end
local instance = SpawnPrefab ( product )
if instance ~= nil then
instance.Transform : SetPosition ( x , y , z )
instance : ListenForEvent ( " onremove " , EntityDeathEventHandler , nil )
2018-01-23 10:13:21 +00:00
2018-01-27 09:23:18 +00:00
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Spawned a " .. product .. " at " .. GetCoordStr ( x , y , z ) .. " for " .. prefab .. " at " .. GetPosStr ( position ) .. " with radius " .. rand_radius .. " tile: " .. TheWorld.Map : GetTileAtPoint ( x , y , z ) )
2018-01-23 10:13:21 +00:00
end
2018-01-27 09:23:18 +00:00
if DEBUG_TELE then
c_teleport ( x , 0 , z )
end
2018-01-23 10:13:21 +00:00
end
2018-01-27 09:23:18 +00:00
2018-02-03 02:53:04 +00:00
return status
2018-01-23 10:13:21 +00:00
end
2018-01-27 09:23:18 +00:00
local function HookEntities ( prefab )
2018-01-23 10:13:21 +00:00
while next ( Ents ) == nil do
end
2018-01-27 09:23:18 +00:00
2018-01-23 10:13:21 +00:00
local count = 0
for k , v in pairs ( Ents ) do
2018-01-27 09:23:18 +00:00
if v.prefab == prefab then
2018-01-23 10:13:21 +00:00
v : RemoveEventCallback ( " onremove " , EntityDeathEventHandler , nil )
v : ListenForEvent ( " onremove " , EntityDeathEventHandler , nil )
count = count + 1
end
end
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Hooked " .. count .. " " .. prefab )
2018-01-27 09:23:18 +00:00
end
end
2019-10-27 08:52:14 +00:00
local function HookAllRegisteredPrefabs ( )
2018-01-27 09:23:18 +00:00
local count = 0
2019-10-27 08:52:14 +00:00
for guid , ent in pairs ( Ents ) do
if regrowth_table [ ent.prefab ] ~= nil then
ent : RemoveEventCallback ( " onremove " , EntityDeathEventHandler , nil )
ent : ListenForEvent ( " onremove " , EntityDeathEventHandler , nil )
count = count + 1
2018-01-27 09:23:18 +00:00
end
2019-10-27 08:52:14 +00:00
end
if DEBUG then
print ( " [ExtraRegrowth] Hooked " .. count .. " entities " )
2018-01-23 10:13:21 +00:00
end
end
--------------------------------------------------------------------------
--[[ Public member functions ]]
--------------------------------------------------------------------------
2018-02-01 06:04:37 +00:00
function self : GetUpdatePeriod ( )
return UPDATE_PERIOD
end
2018-01-23 10:13:21 +00:00
function self : FinishModConfig ( )
2019-10-27 08:52:14 +00:00
HookAllRegisteredPrefabs ( )
ready = true
2018-01-23 10:13:21 +00:00
end
2018-01-27 09:23:18 +00:00
function self : RegisterRegrowth ( prefab , product , interval )
2018-01-29 10:33:28 +00:00
if interval == nil then
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] WARNING: interval for prefab " .. prefab .. " is null. Using default. " )
2018-01-29 10:33:28 +00:00
end
interval = 480
end
2019-10-27 08:52:14 +00:00
if DEBUG then
interval = interval / 100
end
2018-01-23 10:13:21 +00:00
if regrowth_table [ prefab ] == nil then
-- avoid duplicate registration
2018-01-27 09:23:18 +00:00
regrowth_table [ prefab ] =
{
product = product ,
interval = interval
}
2018-01-28 00:44:49 +00:00
2018-01-23 10:13:21 +00:00
end
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Registered product " .. product .. " for prefab " .. prefab .. " with interval " .. interval )
2018-01-23 10:13:21 +00:00
end
end
--------------------------------------------------------------------------
--[[ Initialization ]]
--------------------------------------------------------------------------
2019-10-27 08:52:14 +00:00
inst : DoTaskInTime ( 0 , function ( ) HookAllRegisteredPrefabs ( ) end )
inst : DoPeriodicTask ( UPDATE_PERIOD , function ( ) self : LongUpdate ( ) end )
inst : ListenForEvent ( " ms_cyclecomplete " , function ( ) HookAllRegisteredPrefabs ( ) end ) -- every ~ 1 day we rehook every entities
2018-01-23 10:13:21 +00:00
--------------------------------------------------------------------------
--[[ Update ]]
--------------------------------------------------------------------------
2018-01-29 20:24:22 +00:00
local function RegrowPrefabTask ( prefab , data )
2018-02-01 11:33:38 +00:00
local rand_radius = JITTER_RADIUS + data.retry * INC_RADIUS
if rand_radius > MAX_RADIUS then
rand_radius = MAX_RADIUS
end
local success = TryRegrowth ( prefab , regrowth_table [ prefab ] . product , data.position , rand_radius )
if success == REGROW_STATUS.SUCCESS then
2018-01-29 20:24:22 +00:00
data.remove = true
2018-01-27 09:23:18 +00:00
end
2018-02-01 11:33:38 +00:00
2019-10-27 08:52:14 +00:00
if CACHE_RETRY [ success ] == nil then
-- only increase radius when not cached
2018-02-01 11:33:38 +00:00
data.retry = data.retry + 1
end
2018-01-27 09:23:18 +00:00
end
2019-10-27 08:52:14 +00:00
function self : LongUpdate ( )
if not ready then
2018-01-23 10:13:21 +00:00
-- do nothing if the table is not fully initialized
-- in case we accidentally drop some saved entities due to the respawn_table[prefab] == nil check
return
end
2018-01-27 09:23:18 +00:00
local count = 0
2018-01-23 10:13:21 +00:00
for prefab in pairs ( entity_list ) do
if entity_list [ prefab ] == nil or # entity_list [ prefab ] == 0 then
-- only do meaningful work
else
if regrowth_table [ prefab ] == nil then
-- if we don't have it registered, discard
entity_list [ prefab ] = nil
else
2018-01-29 20:24:22 +00:00
for i = # entity_list [ prefab ] , 1 , - 1 do
if entity_list [ prefab ] [ i ] . remove then
-- handle expired objects first
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Removed prefab " .. prefab .. " at " .. GetPosStr ( entity_list [ prefab ] [ i ] . position ) .. " from the entity list. " )
2018-01-29 20:24:22 +00:00
end
table.remove ( entity_list [ prefab ] , i )
2018-01-27 09:23:18 +00:00
else
2018-01-29 20:24:22 +00:00
-- 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
2018-01-27 09:23:18 +00:00
2018-01-29 20:24:22 +00:00
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Prefab " .. prefab .. " at " .. GetPosStr ( entity_list [ prefab ] [ i ] . position ) .. " has interval " .. entity_list [ prefab ] [ i ] . interval )
2018-01-29 20:24:22 +00:00
end
2018-01-27 09:23:18 +00:00
2018-01-29 20:24:22 +00:00
if entity_list [ prefab ] [ i ] . interval == 0 then
-- different threads
local data = entity_list [ prefab ] [ i ]
2018-01-27 09:23:18 +00:00
2019-10-27 08:52:14 +00:00
RegrowPrefabTask ( prefab , data )
2018-01-23 10:13:21 +00:00
end
end
end
end
end
end
end
--------------------------------------------------------------------------
--[[ Save/Load ]]
--------------------------------------------------------------------------
function self : OnSave ( )
2018-01-27 09:23:18 +00:00
local data = {
entities = { }
}
for prefab in pairs ( entity_list ) do
2018-01-28 00:44:49 +00:00
if entity_list [ prefab ] ~= nil then
-- could be nil (set in the event loop)
data.entities [ prefab ] = { }
for i = 1 , # entity_list [ prefab ] do
2018-02-01 11:33:38 +00:00
data.entities [ prefab ] [ # data.entities [ prefab ] + 1 ] =
{
interval = entity_list [ prefab ] [ i ] . interval ,
position =
{
x = entity_list [ prefab ] [ i ] . position.x ,
y = entity_list [ prefab ] [ i ] . position.y ,
z = entity_list [ prefab ] [ i ] . position.z
} ,
remove = entity_list [ prefab ] [ i ] . remove ,
retry = entity_list [ prefab ] [ i ] . retry
}
2018-01-28 00:44:49 +00:00
end
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Saved " .. # data.entities [ prefab ] .. " entities for prefab " .. prefab )
2018-01-28 00:44:49 +00:00
end
2018-01-27 09:23:18 +00:00
end
end
return data
2018-01-23 10:13:21 +00:00
end
function self : OnLoad ( data )
2018-01-27 09:23:18 +00:00
for prefab in pairs ( data.entities ) do
if entity_list [ prefab ] == nil then
entity_list [ prefab ] = { }
2018-01-28 00:44:49 +00:00
for i = 1 , # data.entities [ prefab ] do
2018-02-01 11:33:38 +00:00
entity_list [ prefab ] [ # entity_list [ prefab ] + 1 ] =
{
interval = data.entities [ prefab ] [ i ] . interval ,
position =
{
x = data.entities [ prefab ] [ i ] . position.x ,
y = data.entities [ prefab ] [ i ] . position.y ,
z = data.entities [ prefab ] [ i ] . position.z
} ,
remove = ( data.entities [ prefab ] [ i ] . remove == nil ) and false or data.entities [ prefab ] [ i ] . remove ,
retry = ( data.entities [ prefab ] [ i ] . retry == nil ) and 0 or data.entities [ prefab ] [ i ] . retry
}
2018-01-28 00:44:49 +00:00
end
if DEBUG then
2019-10-27 08:52:14 +00:00
print ( " [ExtraRegrowth] Loaded " .. # entity_list [ prefab ] .. " entities for prefab " .. prefab )
2018-01-28 00:44:49 +00:00
end
2018-01-27 09:23:18 +00:00
end
end
2018-01-23 10:13:21 +00:00
end
--------------------------------------------------------------------------
--[[ End ]]
--------------------------------------------------------------------------
end )