openclonk/planet/Objects.ocd/Libraries.ocd/Plants.ocd/Tree.ocd/Script.c

313 lines
8.3 KiB
C

/**
Tree
Basic functionality for everything choppable (trees).
Must be included after Library_Plant!
@author Clonkonaut
*/
/** Return true if this a 'burned tree'. It will not grow or seed then.
*/
public func IsBurnedTree()
{
return false;
}
/* Placement */
// Overload Place to add the foreground parameter, foreground = true will roughly make every 3rd tree foreground (not the offspring though)
public func Place(int amount, proplist area, proplist settings, bool foreground)
{
// Default behaviour
var trees = _inherited(amount, area, settings);
if (GetLength(trees) < 1) return trees;
for (var tree in trees)
if (!Random(3))
tree.Plane = 510;
return trees;
}
/* Creation */
/** Sets a random mesh rotation and Growth(5) if no growth set.
*/
private func Construction()
{
_inherited(...);
SetProperty("MeshTransformation", Trans_Rotate(RandomX(0,359),0,1,0));
if (!GetGrowthValue() && !IsBurnedTree()) StartGrowth(5);
if (IsBurnedTree()) RemoveTimer("Seed");
lib_tree_attach = CreateArray();
}
/* Placement of fruit or other objects in the tree top */
/** An array containing any objects attached in the treetop.
*/
local lib_tree_attach;
/** Returns a random point somewhere in treetop. Should be overloaded.
pos is proplist with x and y
*/
public func GetTreetopPosition(proplist pos)
{
}
/** Creates a new object of type to_create in the treetop.
If no_overlap_check is true, it is not guaranteed that the object won't overlap with existing (already in the treetop) ones. tries will be ignored.
If no_overlap_check is false, it is not guaranteed that the object will be created! Only tries (default 20) random positions are checked.
*/
public func CreateObjectInTreetop(id to_create, int tries, bool no_overlap_check)
{
if (!to_create) return FatalError("to_create can't be nil");
if (!IsStanding()) return nil;
if (!tries) tries = 20;
var pos = { x=0, y=0 }, overlap, width, height, area;
do {
GetTreetopPosition(pos);
overlap = false;
if (no_overlap_check) break;
for (var object in lib_tree_attach)
{
if (!object) continue;
width = object->GetObjWidth();
height = object->GetObjHeight();
area = Shape->Rectangle(object->GetX()-GetX()-width/2, object->GetY()-GetY()-height/2, width, height);
if (area->IsPointContained(pos.x, pos.y))
{
overlap = true;
break;
}
if (overlap) continue;
break;
}
} while(--tries);
if (overlap) return nil;
var attach = CreateObject(to_create, pos.x, pos.y, GetOwner());
RemoveHoles(lib_tree_attach);
lib_tree_attach[GetLength(lib_tree_attach)] = attach;
// The object probably wants to do stuff now
attach->~AttachToTree(this);
attach.Plane = this.Plane + Random(2)*2-1;
return attach;
}
// Whenever something happens to the tree (e.g. axe hit or fire). Afterwards all fruit/objects are considered "dropped" and no longer saved.
private func DetachObjects()
{
RemoveHoles(lib_tree_attach);
if (!GetLength(lib_tree_attach)) return;
for (var object in lib_tree_attach)
if (object) object->~DetachFromTree();
lib_tree_attach = CreateArray();
}
/* Chopping */
/** Determines whether this plant gives wood (we assume that are 'trees').
@return \c true if the plant is choppable by the axe (default), \c false otherwise.
*/
public func IsTree()
{
return true;
}
/** 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. Default is 50.
@return \c the maximum amount of damage.
*/
private func MaxDamage()
{
return 50;
}
/** Define a burned definition of the tree. If nil, the tree will burst into ashes.
*/
local lib_tree_burned;
private func Damage(int change, int cause)
{
// do not grow for a few seconds
var g = GetGrowthValue();
if(g)
{
StopGrowth();
ScheduleCall(this, "RestartGrowth", 36 * 10, 0, g);
}
// Max damage reached: burn or fall down
if (GetDamage() > MaxDamage())
{
// Burn
if (OnFire())
{
DetachObjects();
if (!lib_tree_burned) return BurstIntoAshes();
var burned = CreateObject(lib_tree_burned, 0, 0, GetOwner());
burned->SetCategory(GetCategory());
burned.Touchable = this.Touchable;
burned->SetCon(GetCon());
if (burned)
{
burned->SetR(GetR());
burned->Incinerate(OnFire());
burned->SetPosition(GetX(), GetY());
return RemoveObject();
}
// Something went wrong. Better Burst into ashes!
return BurstIntoAshes();
}
// Fall down
if (IsStanding())
return this->ChopDown();
}
if (cause == FX_Call_DmgChop && IsStanding())
ShakeTree();
}
// Restarts the growing of the tree (for example after taking damage)
private func RestartGrowth(int old_value)
{
var g = GetGrowthValue(); // safety
if(g) StopGrowth();
g = Max(g, old_value);
StartGrowth(g);
}
// Visual chopping effect
private func ShakeTree()
{
var effect = AddEffect("IntShakeTree", this, 100, 1, this);
effect.current_trans = this.MeshTransformation;
DetachObjects();
}
private func FxIntShakeTreeTimer(object target, proplist effect, int time)
{
if (time > 24)
return FX_Execute_Kill;
var angle = Sin(time * 45, 2);
var r = Trans_Rotate(angle, 0, 0, 1);
target.MeshTransformation = Trans_Mul(r, effect.current_trans);
return FX_OK;
}
public func BurstIntoAshes()
{
var particles =
{
Prototype = Particles_Dust(),
R = 50, G = 50, B = 50,
Size = PV_KeyFrames(0, 0, 0, 200, PV_Random(2, 10), 1000, 0),
};
var r = GetR();
var size = GetCon() * 110 / 100;
for(var cnt = 0; cnt < 10; ++cnt)
{
var distance = Random(size/2);
var x = Sin(r, distance);
var y = -Cos(r, distance);
for(var mirror = -1; mirror <= 1; mirror += 2)
{
CreateParticle("Dust", x * mirror, y * mirror, PV_Random(-3, 3), PV_Random(-3, -3), PV_Random(18, 1 * 36), particles, 2);
CastPXS("Ashes", 5, 30, x * mirror, y * mirror);
}
}
RemoveObject();
}
/** Called when the trees shall fall down (has taken max damage).
Default behaviour is too remove the first vertex (ensure it's the bottom one), unstucking (5 pixel movement max) and removing C4D_StaticBack.
*/
public func ChopDown()
{
// stop growing!
ClearScheduleCall(this, "RestartGrowth");
StopGrowth();
// stop reproduction
RemoveTimer("Seed");
DetachObjects();
this.Touchable = 1;
this.Plane = 300;
SetCategory(GetCategory()&~C4D_StaticBack);
// Use Special Vertex Mode 1 (see documentation) so the removed vertex won't come back when rotating the tree.
SetVertex(0, VTX_Y, 0, 1);
RemoveVertex(0);
// Still stuck?
if (Stuck())
{
var i = 5;
while(Stuck() && i)
{
SetPosition(GetX(), GetY()-1);
i--;
}
}
Sound("Environment::Tree::Crack");
AddEffect("TreeFall", this, 1, 1, this);
}
// determine a random falling direction and passes it on to the FxTreeFallTimer.
private 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. */
private 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("Environment::Tree::Landing", 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;
}
}