forked from Mirrors/openclonk
264 lines
6.7 KiB
C
264 lines
6.7 KiB
C
/**
|
|
Plant
|
|
Basic functionality for all plants
|
|
|
|
@author Clonkonaut
|
|
*/
|
|
|
|
// This is a plant
|
|
public func IsPlant()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/** Automated positioning via RootSurface, make sure to call this if needed (in case Construction is overloaded)
|
|
*/
|
|
protected func Construction(...)
|
|
{
|
|
Schedule(this, "RootSurface()", 1);
|
|
UpdateSeedTimer();
|
|
AddTimer("Seed", 72 + Random(10));
|
|
_inherited(...);
|
|
}
|
|
|
|
public func InitChild(object parent)
|
|
{
|
|
// Copy settings from parent plant
|
|
KeepArea(parent.Confinement);
|
|
SetSeedChance(parent->SeedChance());
|
|
SetSeedArea(parent->SeedArea());
|
|
SetSeedAmount(parent->SeedAmount());
|
|
SetSeedOffset(parent->SeedOffset());
|
|
return true;
|
|
}
|
|
|
|
/* Placement */
|
|
|
|
/** Places the given amount of plants inside the area. If no area is given, the whole landscape is used.
|
|
@param amount The amount of plants to be created (not necessarily every plant is created).
|
|
@param rectangle The area where to put the plants.
|
|
@param settings A proplist defining further setttings: { growth = 100000, keep_area = false }. Growth will get passed over to PlaceVegetation, keep_area will confine the plants and their offspring to rectangle.
|
|
@return Returns an array of all objects created.
|
|
*/
|
|
public func Place(int amount, proplist area, proplist settings)
|
|
{
|
|
// No calls to objects, only definitions
|
|
if (GetType(this) == C4V_C4Object) return;
|
|
// Default parameters
|
|
if (!settings) settings = { growth = 100000, keep_area = false };
|
|
if (!settings.growth) settings.growth = 100000;
|
|
var rectangle;
|
|
if (area) rectangle = area->GetBoundingRectangle(); else rectangle = Shape->LandscapeRectangle();
|
|
|
|
var plants = CreateArray(), plant;
|
|
for (var i = 0 ; i < amount ; i++)
|
|
{
|
|
plant = PlaceVegetation(this, rectangle.x, rectangle.y, rectangle.wdt, rectangle.hgt, settings.growth, area);
|
|
if (plant)
|
|
{
|
|
plants[GetLength(plants)] = plant;
|
|
if (settings.keep_area && area)
|
|
plant->KeepArea(area);
|
|
}
|
|
plant = nil;
|
|
}
|
|
return plants;
|
|
}
|
|
|
|
/* Reproduction */
|
|
|
|
/** Will confine the the plant and its offspring to a certain area.
|
|
@params area The confinement area.
|
|
*/
|
|
func KeepArea(proplist area)
|
|
{
|
|
this.Confinement = area;
|
|
}
|
|
|
|
/** Chance to reproduce plant. Chances are one out of return value. From 0 to 10000. Default is 20.
|
|
@return the chance, higher = more chance. 0 = does not reproduce.
|
|
*/
|
|
|
|
local plant_seed_chance = 20;
|
|
|
|
public func SeedChance()
|
|
{
|
|
return plant_seed_chance;
|
|
}
|
|
|
|
public func SetSeedChance(int v)
|
|
{
|
|
plant_seed_chance = v;
|
|
return UpdateSeedTimer();
|
|
}
|
|
|
|
private func UpdateSeedTimer()
|
|
{
|
|
RemoveTimer("Seed");
|
|
if (plant_seed_chance) AddTimer("Seed", 72 + Random(10));
|
|
return true;
|
|
}
|
|
|
|
/** Distance the seeds may travel. Default is 250.
|
|
@return the maximum distance.
|
|
*/
|
|
|
|
local plant_seed_area = 250;
|
|
|
|
public func SeedArea()
|
|
{
|
|
return plant_seed_area;
|
|
}
|
|
|
|
public func SetSeedArea(int v)
|
|
{
|
|
plant_seed_area = v;
|
|
return true;
|
|
}
|
|
|
|
/** The amount of plants allowed within SeedAreaSize. Default is 10.
|
|
@return the maximum amount of plants.
|
|
*/
|
|
|
|
local plant_seed_amount = 10;
|
|
|
|
private func SeedAmount()
|
|
{
|
|
return plant_seed_amount;
|
|
}
|
|
|
|
public func SetSeedAmount(int v)
|
|
{
|
|
plant_seed_amount = v;
|
|
return true;
|
|
}
|
|
|
|
/** The closest distance a new plant may seed to its nearest neighbour. Default is 20.
|
|
@return the closest distance to another plant.
|
|
*/
|
|
|
|
local plant_seed_offset = 20;
|
|
|
|
public func SeedOffset()
|
|
{
|
|
return plant_seed_offset;
|
|
}
|
|
|
|
public func SetSeedOffset(int v)
|
|
{
|
|
plant_seed_offset = v;
|
|
return true;
|
|
}
|
|
|
|
/** Evaluates parameters for this definition to determine if seeding should occur.
|
|
@par offx X offset added to context position for check.
|
|
@par offy Y offset added to context position for check.
|
|
@plant_id plant to check for whether it's already too crowded. Default to GetID().
|
|
@return true iff seeding should occur
|
|
*/
|
|
public func CheckSeedChance(int offx, int offy, id plant_id)
|
|
{
|
|
// Find number of plants in seed area.
|
|
// Ignored confinement - that's only used for actual placement
|
|
var size = this->SeedArea();
|
|
var amount = this->SeedAmount();
|
|
var plant_cnt = ObjectCount(Find_ID(plant_id ?? GetID()), Find_InRect(offx - size / 2, offy - size / 2, size, size));
|
|
// Increase seed chance by number of missing plants to reach maximum amount
|
|
// Note the chance will become negative if the maximum has been reached, in which case the random check will never succeed.
|
|
// That's intended
|
|
var chance = this->SeedChance() * (amount - plant_cnt);
|
|
if (!chance) return;
|
|
// Place a plant if we are lucky
|
|
return (Random(10000) < chance);
|
|
}
|
|
|
|
/** Reproduction of plants: Called every 2 seconds by a timer.
|
|
*/
|
|
private func Seed()
|
|
{
|
|
if (OnFire()) return;
|
|
|
|
// Place a plant if we are lucky, but no more than seed amount.
|
|
var plant;
|
|
if (CheckSeedChance())
|
|
{
|
|
plant = DoSeed(true);
|
|
// Check if it is not close to another one.
|
|
if (plant)
|
|
{
|
|
var neighbours = FindObjects(Find_Func("IsPlant"), Find_Exclude(plant),
|
|
Sort_Multiple(Sort_Distance(plant->GetX() - GetX(), plant->GetY() - GetY()), Sort_Reverse(Sort_Func("SeedOffset"))));
|
|
// Only check the nearest 3 plants
|
|
var too_close = false;
|
|
for (var i = 0; i < GetLength(neighbours) && i < 3; i++)
|
|
{
|
|
var neighbour = neighbours[i];
|
|
var x_distance = plant->SeedOffset() + 1;
|
|
var y_distance = 151;
|
|
if (neighbour)
|
|
{
|
|
x_distance = Abs(neighbour->GetX() - plant->GetX());
|
|
y_distance = Abs(neighbour->GetY() - plant->GetY());
|
|
}
|
|
if ((x_distance < plant->SeedOffset() || x_distance < neighbour->~SeedOffset()) && y_distance < 151)
|
|
{
|
|
too_close = true;
|
|
break;
|
|
}
|
|
}
|
|
// Closeness check
|
|
if (too_close)
|
|
plant->RemoveObject();
|
|
else
|
|
plant->InitChild(this);
|
|
}
|
|
}
|
|
return plant;
|
|
}
|
|
|
|
/** Forcefully places a seed of the plant, without random chance
|
|
or other sanity checks. This is useful for testing.
|
|
*/
|
|
public func DoSeed(bool no_init)
|
|
{
|
|
// Apply confinement for plant placement
|
|
var size = SeedArea();
|
|
var area = Shape->Rectangle(GetX() - size / 2, GetY() - size / 2, size, size);
|
|
var confined_area = nil;
|
|
if (this.Confinement)
|
|
{
|
|
confined_area = Shape->Intersect(this.Confinement, area);
|
|
// Quick-check if intersection to confinement yields an empty area
|
|
// to avoid unnecessery search by PlaceVegetation
|
|
area = confined_area->GetBoundingRectangle();
|
|
if (area.wdt <= 0 || area.hgt <= 0) return;
|
|
}
|
|
else
|
|
{
|
|
// Place the new plant in the original area
|
|
confined_area = area;
|
|
}
|
|
// Place the plant
|
|
var plant = PlaceVegetation(GetID(), 0, 0, 0, 0, 3, confined_area);
|
|
if (!no_init && plant)
|
|
plant->InitChild(this);
|
|
|
|
return plant;
|
|
}
|
|
|
|
private func RemoveInTunnel()
|
|
{
|
|
if (GetMaterial() == Material("Tunnel") || GetMaterial(0, -10) == Material("Tunnel"))
|
|
{
|
|
RemoveObject();
|
|
}
|
|
}
|
|
|
|
/* Editor */
|
|
|
|
public func Definition(def, ...)
|
|
{
|
|
Library_Seed->AddSeedEditorProps(def);
|
|
return _inherited(def, ...);
|
|
}
|