/** 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); AddTimer("Seed", 72); _inherited(...); } /* 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 rectangle, 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; if (!rectangle) rectangle = Rectangle(0,0, LandscapeWidth(), LandscapeHeight()); var plants = CreateArray(), plant; for (var i = 0 ; i < amount ; i++) { plant = PlaceVegetation(this, rectangle.x, rectangle.y, rectangle.w, rectangle.h, settings.growth); if (plant) { plants[GetLength(plants)] = plant; if (settings.keep_area) plant->KeepArea(rectangle); } plant = nil; } return plants; } /* Reproduction */ /** Will confine the the plant and its offspring to a certain area. @params rectangle The confinement area. */ func KeepArea(proplist rectangle) { this.Confinement = rectangle; } /** Chance to reproduce plant. Chances are one out of return value. Default is 500. @return the chance, higher = less chance. */ private func SeedChance() { return 500; } /** Distance the seeds may travel. Default is 250. @return the maximum distance. */ private func SeedArea() { return 250; } /** The amount of plants allowed within SeedAreaSize. Default is 10. @return the maximum amount of plants. */ private func SeedAmount() { return 10; } /** The closest distance a new plant may seed to its nearest neighbour. Default is 20. @return the maximum amount of plants. */ private func SeedOffset() { return 20; } /** Reproduction of plants: Called every 2 seconds by a timer. */ public func Seed() { // Find number of plants in seed area. var size = SeedArea(); var amount = SeedAmount(); var area = Rectangle(size / -2, size / -2, size, size); if (this.Confinement) area = RectangleEnsureWithin(area, this.Confinement); var plant_cnt = ObjectCount(Find_ID(GetID()), Find_InRect(area.x, area.y, area.w, area.h)); // If there are not much plants in the seed area compared to seed amount // the chance of seeding is improved, if there are much the chance is reduced. var chance = SeedChance(); var chance = chance / Max(1, amount - plant_cnt) + chance * Max(0, plant_cnt - amount); // Place a plant if we are lucky, but no more than seed amount. if (plant_cnt < amount && !Random(chance)) { // Place the plant but check if it is not close to another one. var plant = PlaceVegetation(GetID(), area.x, area.y, area.w, area.h, 3); if (plant) { var neighbour = FindObject(Find_ID(GetID()), Find_Exclude(plant), Sort_Distance(plant->GetX() - GetX(), plant->GetY() - GetY())); var distance = ObjectDistance(plant, neighbour); // Closeness check if (distance < SeedOffset()) plant->RemoveObject(); else if (this.Confinement) plant->KeepArea(this.Confinement); } } return; } /* Chopping */ /** Determines whether this plant gives wood (we assume that are 'trees'). @return \c true if the plant is choppable by the axe, \c false otherwise (default). */ public func IsTree() { return false; } /** Determines whether the tree can still be chopped down (i.e. has not been chopped down). @return \c true if the tree is still a valid axe target. */ public func IsStanding() { return GetCategory() & C4D_StaticBack; } /** Maximum damage the tree can take before it falls. Each blow from the axe deals 10 damage. @return \c the maximum amount of damage. */ private func MaxDamage() { return 50; } protected func Damage() { // do not grow for a few seconds var g = GetGrowthValue(); if(g) { StopGrowth(); ScheduleCall(this, "RestartGrowth", 36 * 10, 0, g); } // Max damage reached -> fall down if (GetDamage() > MaxDamage() && IsStanding()) ChopDown(); _inherited(...); } // restarts the growing of the tree (for example after taking damage) func RestartGrowth(int old_value) { var g = GetGrowthValue(); // safety if(g) StopGrowth(); g = Max(g, old_value); StartGrowth(g); } /** Called when the trees shall fall down (has taken max damage). Default behaviour is unstucking (5 pixel movement max) and removing C4D_StaticBack. */ public func ChopDown() { // stop growing! ClearScheduleCall(this, "RestartGrowth"); StopGrowth(); this.Touchable = 1; this.Plane = 300; SetCategory(GetCategory()&~C4D_StaticBack); if (Stuck()) { var i = 5; while(Stuck() && i) { SetPosition(GetX(), GetY()-1); i--; } } Sound("TreeCrack"); AddEffect("TreeFall", this, 1, 1, this); } // determine a random falling direction and passes it on to the FxTreeFallTimer. func FxTreeFallStart(object target, proplist effect) { effect.direction = Random(2); if (effect.direction == 0) effect.direction -= 1; } /* animates the falling of the tree: First 10 slow degress then speed up and play the landing sound at 80+ degrees. remember that degrees range from -180 to 180. */ func FxTreeFallTimer(object target, proplist effect) { //simple falling if tree is not fully grown if (target->GetCon() <= 50) { target->SetRDir(effect.direction * 10); } //else rotate slowly first until about 10 degree. This will be the time needed for the crack sound and makes sense as a tree will start falling slowly. else { if (Abs(target->GetR()) < 10) { target->SetRDir(effect.direction * 1); //Turn of gravity so the tree doesn't get stuck before its done falling. target->SetYDir(0); } else { //Then speed up and let gravity do the rest. target->SetRDir(effect.direction * 10); } } //if the tree does not lend on a cliff or sth. (is rotated more then 80 degrees in the plus or minus direction) Play the landing sound of the tree. if (Abs(target->GetR()) > 80) { target->SetRDir(0); if (target->GetCon() > 50) target->Sound("TreeLanding", false); return -1; } //check every frame if the tree is stuck and stop rotation in that case this is necessary as a tree could get stuck before reaching 80 degrees if ((target->GetContact(-1, CNAT_Left) | target->GetContact(-1, CNAT_Right)) > 0) { target->SetRDir(0); return -1; } }