forked from Mirrors/openclonk
356 lines
12 KiB
C
356 lines
12 KiB
C
/*
|
|
Cotton
|
|
Author: Clonkonaut
|
|
|
|
Medium crop for farming.
|
|
*/
|
|
|
|
#include Library_Plant
|
|
#include Library_Crop
|
|
|
|
local direction = 1;
|
|
local capacity; // How much fruit this plant will yield
|
|
local branches; // Array with 3 proplists containing all the needed values for the 3 branches
|
|
local branch_proto = {
|
|
// As returned by AttachMesh
|
|
attach_slot = 0,
|
|
// Growing time until ready to bear fruit, growing time afterwards is determined by the fruit
|
|
grow_time = 4500,
|
|
// The branch's animation length is 875, 490 marks the point just before the branch start raising up
|
|
first_animation_stage = 490,
|
|
// Whether the branch is ready to bear fruit yet
|
|
grown = false,
|
|
// As returned by PlayAnimation
|
|
grow_animation = -1,
|
|
// As returned by AddEffect
|
|
grow_effect = nil,
|
|
// The fruit object (in Contents)
|
|
fruit = nil,
|
|
// As returned by AttachMesh
|
|
fruit_slot = 0,
|
|
// Internal value to (securely) stop new fruit from growing at this branch
|
|
no_fruit = false,
|
|
};
|
|
|
|
private func Construction()
|
|
{
|
|
_inherited(...);
|
|
RemoveTimer("Seed");
|
|
capacity = Random(3) + 3;
|
|
|
|
StartGrowth(this.growth);
|
|
AddTimer("WaterCheck", 70+Random(10));
|
|
|
|
// The mesh doesn't have more than 3 bones, beware
|
|
branches = CreateArray(3);
|
|
branches[0] = new branch_proto {};
|
|
branches[1] = new branch_proto {};
|
|
branches[2] = new branch_proto {};
|
|
|
|
// Make half of the plants switch direction for more variety
|
|
if (!Random(2)) direction = -1;
|
|
SetProperty("MeshTransformation", Trans_Rotate(RandomX(70,110) * direction, 0,1,0));
|
|
}
|
|
|
|
private func Initialize()
|
|
{
|
|
AddTimer("Reproduction", 72);
|
|
}
|
|
|
|
/* Reproduction */
|
|
|
|
private func Reproduction()
|
|
{
|
|
if (OnFire()) return;
|
|
if (GetCon() < 100) return RemoveTimer("Reproduction");
|
|
if (!capacity && !HasFruit()) return Perish();
|
|
|
|
if (CheckSeedChance())
|
|
{
|
|
var can_branch = GetNextGrowableBranch();
|
|
var can_fruit = GetNextFruitableBranch();
|
|
if ((can_branch != -1 && Random(3)) || can_fruit == -1)
|
|
return GrowBranch();
|
|
if (can_fruit != -1)
|
|
{
|
|
GrowFruit();
|
|
capacity--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The plant will wither away after it has grown all its fruit
|
|
private func Perish()
|
|
{
|
|
RemoveTimer("Reproduction");
|
|
StartGrowth(this.degrowth);
|
|
SetClrModulation(RGB(185, 122, 87));
|
|
}
|
|
|
|
/* Branch growth */
|
|
|
|
// Will grow the next branch unless all 3 are grown (returns false then, true otherwise)
|
|
// If fullgrown is true, the branch will be fully grown but not bear a fruit!
|
|
public func GrowBranch(bool fullgrown, int branch)
|
|
{
|
|
var next_to_grow;
|
|
if (branch != nil)
|
|
next_to_grow = branch;
|
|
else
|
|
next_to_grow = GetNextGrowableBranch(fullgrown);
|
|
if (next_to_grow == -1) return false;
|
|
|
|
if (branches[next_to_grow].grow_animation != branch_proto.grow_animation) // Already growing, fullgrown must be true
|
|
{
|
|
if (!fullgrown) return false; // This can't happen
|
|
FinishBranchGrowth(next_to_grow);
|
|
return true;
|
|
}
|
|
branches[next_to_grow].attach_slot = AttachMesh(Cotton_Branch, Format("Stem%d", next_to_grow + 1), "main", GetBranchAttachTransform(next_to_grow, 1));
|
|
branches[next_to_grow].grow_animation = PlayAnimation("grow", next_to_grow + 1, Anim_Linear(0,0, branches[next_to_grow].first_animation_stage, branches[next_to_grow].grow_time, ANIM_Hold), Anim_Const(1000), nil, branches[next_to_grow].attach_slot);
|
|
branches[next_to_grow].grow_effect = AddEffect("IntBranchGrowth", this, 1, 35, this);
|
|
branches[next_to_grow].grow_effect.branch = next_to_grow;
|
|
if (fullgrown) FinishBranchGrowth(next_to_grow);
|
|
return true;
|
|
}
|
|
|
|
// Determines the next branch to grow. If fullgrown is true, growing but unfinished branches are also returned
|
|
private func GetNextGrowableBranch(bool fullgrown)
|
|
{
|
|
var ret = -1;
|
|
var i = -1;
|
|
while(++i < GetLength(branches))
|
|
{
|
|
if (branches[i].grown) continue;
|
|
if (branches[i].grow_animation != branch_proto.grow_animation && !fullgrown) continue;
|
|
ret = i;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
private func FxIntBranchGrowthTimer(object target, proplist effect, int time)
|
|
{
|
|
UpdateBranchAttachTransform(effect.branch, GetAnimationPosition(branches[effect.branch].grow_animation, branches[effect.branch].attach_slot) * 500 / branches[effect.branch].first_animation_stage);
|
|
if (GetAnimationPosition(branches[effect.branch].grow_animation, branches[effect.branch].attach_slot) >= branches[effect.branch].first_animation_stage)
|
|
FinishBranchGrowth(effect.branch);
|
|
}
|
|
|
|
private func FinishBranchGrowth(int branch)
|
|
{
|
|
branches[branch].grown = true;
|
|
RemoveEffect(nil, nil, branches[branch].grow_effect);
|
|
UpdateBranchAttachTransform(branch, 500);
|
|
SetAnimationPosition(branches[branch].grow_animation, Anim_Const(branches[branch].first_animation_stage), branches[branch].attach_slot);
|
|
}
|
|
|
|
private func UpdateBranchAttachTransform(int branch, int scale)
|
|
{
|
|
SetAttachTransform(branches[branch].attach_slot, GetBranchAttachTransform(branch, scale));
|
|
}
|
|
|
|
private func GetBranchAttachTransform(int branch, int scale)
|
|
{
|
|
// These transforms have been determined by careful testing
|
|
if (branch == 0) return Trans_Mul(Trans_Rotate(-30,0,1), Trans_Rotate(-100,0,0,1), Trans_Rotate(-90,0,1), Trans_Translate(1000), Trans_Scale(scale));
|
|
if (branch == 1) return Trans_Mul(Trans_Rotate(30,0,1), Trans_Rotate(-100,0,0,1), Trans_Rotate(90,0,1), Trans_Translate(1000), Trans_Scale(scale));
|
|
if (branch == 2) return Trans_Mul(Trans_Rotate(-100,0,0,1), Trans_Rotate(-90,0,1), Trans_Scale(scale));
|
|
}
|
|
|
|
/* Fruit growth */
|
|
|
|
// Will grow a fruit but only if there's a fully grown branch without a fruit available
|
|
// If fullgrown is true, the fruit will be ripe immediately. If there was no full grown branch, a branch will also be complete.
|
|
// Does return the fruit object or nil
|
|
public func GrowFruit(bool fullgrown)
|
|
{
|
|
var next_to_grow = GetNextFruitableBranch(fullgrown);
|
|
if (next_to_grow == -1) return nil;
|
|
|
|
if (branches[next_to_grow].fruit) // Already growing, fullgrown must be true
|
|
{
|
|
if (!fullgrown) return false; // This can't happen
|
|
branches[next_to_grow].fruit->Grow(next_to_grow, fullgrown);
|
|
return branches[next_to_grow].fruit;
|
|
}
|
|
if (!branches[next_to_grow].grown)
|
|
GrowBranch(fullgrown, next_to_grow);
|
|
|
|
branches[next_to_grow].fruit = CreateContents(Cotton_Fruit);
|
|
if (!branches[next_to_grow].fruit) return false;
|
|
branches[next_to_grow].fruit_slot = AttachMesh(branches[next_to_grow].fruit, "fruit_target", "leafes", GetFruitAttachTransform(1, 0), nil, branches[next_to_grow].attach_slot);
|
|
branches[next_to_grow].fruit->Grow(next_to_grow, fullgrown);
|
|
return branches[next_to_grow].fruit;
|
|
}
|
|
|
|
// Determines the next brnach to grow a fruit. If fullgrown is true, unfinished branches are also returned
|
|
private func GetNextFruitableBranch(bool fullgrown)
|
|
{
|
|
var ret = -1;
|
|
var i = -1;
|
|
while(++i < GetLength(branches))
|
|
{
|
|
if (branches[i].no_fruit) continue;
|
|
if (branches[i].fruit && !(fullgrown && branches[i].fruit->IsGrowing())) continue;
|
|
if (!branches[i].grown && !fullgrown) continue;
|
|
ret = i;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Usually called by the fruit
|
|
public func UpdateFruitAttachTransform(int branch, int scale)
|
|
{
|
|
SetAttachTransform(branches[branch].fruit_slot, GetFruitAttachTransform(scale, 0));
|
|
}
|
|
|
|
private func GetFruitAttachTransform(int scale, int extra_r)
|
|
{
|
|
return Trans_Mul(Trans_Rotate(180, 0,1,0), Trans_Rotate(extra_r, 0,0,1), Trans_Scale(scale));
|
|
}
|
|
|
|
// Called by the fruit when the first growing animation is done
|
|
// The branch will restart its animation with duration of time
|
|
public func FruitFills(int branch, int time, bool fullgrown)
|
|
{
|
|
var pos = GetAnimationPosition(branches[branch].grow_animation, branches[branch].attach_slot);
|
|
if (fullgrown)
|
|
SetAnimationPosition(branches[branch].grow_animation, Anim_Const(GetAnimationLength("grow", branches[branch].attach_slot)), branches[branch].attach_slot);
|
|
else
|
|
SetAnimationPosition(branches[branch].grow_animation, Anim_Linear(pos,
|
|
0,
|
|
GetAnimationLength("grow", branches[branch].attach_slot),
|
|
time,
|
|
ANIM_Hold), branches[branch].attach_slot);
|
|
}
|
|
|
|
// Called by the fruit before it flies off to give fair warning
|
|
public func ShakeFruit(int branch)
|
|
{
|
|
var effect = AddEffect("IntShakeFruit", this, 1, 1, this);
|
|
effect.branch = branch;
|
|
}
|
|
|
|
private func FxIntShakeFruitTimer(object target, proplist effect, int time)
|
|
{
|
|
if (time > 24)
|
|
return FX_Execute_Kill;
|
|
var angle = Sin(time * 45, 4);
|
|
SetAttachTransform(branches[effect.branch].fruit_slot, GetFruitAttachTransform(2000, angle));
|
|
return FX_OK;
|
|
}
|
|
|
|
// Called by the fruit
|
|
// Returns the exit position of the fruit relative to the cotton plant
|
|
// TODO: Do not hardcode positions but get the bone's position in Clonk coordinates (as of now feature is missing)
|
|
public func GetFruitExitPosition(int branch)
|
|
{
|
|
if (branch == 0) return { x = 7 * direction, y = 0};
|
|
if (branch == 1) return { x = -7 * direction, y = -1};
|
|
if (branch == 2) return { x = 11 * direction, y = -16};
|
|
return {}; // Unknown
|
|
}
|
|
|
|
// Called by the fruit when it exits the plant
|
|
// Will reverse the animation to full grown but not with fruit state in 35 frames
|
|
// Accordingly, branches[].fruit will be cleared after 35 frames
|
|
public func DetachFruit(int branch)
|
|
{
|
|
DetachMesh(branches[branch].fruit_slot);
|
|
SetAnimationPosition(branches[branch].grow_animation, Anim_Linear(GetAnimationPosition(branches[branch].grow_animation,
|
|
branches[branch].attach_slot),
|
|
GetAnimationLength("grow",
|
|
branches[branch].attach_slot),
|
|
branches[branch].first_animation_stage,
|
|
35,
|
|
ANIM_Hold), branches[branch].attach_slot);
|
|
ScheduleCall(this, "ClearFruit", 35, nil, branch);
|
|
// This is to ensure that within the next 35 frames no new fruit will grow on this branch
|
|
// This is necessary because the actual fruit object might be deleted before that time
|
|
branches[branch].no_fruit = true;
|
|
}
|
|
|
|
private func ClearFruit(int branch)
|
|
{
|
|
branches[branch].fruit = nil;
|
|
branches[branch].fruit_slot = nil;
|
|
branches[branch].no_fruit = nil;
|
|
}
|
|
|
|
private func HasFruit()
|
|
{
|
|
for (var branch in branches)
|
|
if (branch.fruit)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Crop Library */
|
|
|
|
public func SickleHarvesting()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Only harvestable if it has a ripe fruit
|
|
public func IsHarvestable()
|
|
{
|
|
for (var branch in branches)
|
|
if (branch.fruit)
|
|
if (!branch.fruit->~IsGrowing())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
public func Harvest(object clonk)
|
|
{
|
|
var fruit = -1;
|
|
for (var i = 0; i < GetLength(branches); i++)
|
|
if(branches[i].fruit)
|
|
if(!branches[i].fruit->~IsGrowing())
|
|
{
|
|
fruit = i;
|
|
break;
|
|
}
|
|
if (fruit == -1) return false;
|
|
|
|
branches[fruit].fruit->Fly();
|
|
return true;
|
|
}
|
|
|
|
/*-- Saving --*/
|
|
|
|
public func SaveScenarioObject(proplist props)
|
|
{
|
|
if (!inherited(props, ...)) return false;
|
|
// The fruit are not saved if still inside, do a rudimentary save.
|
|
// Saving all little details of growth is a bit too much for such an always changing state.
|
|
for (var i = 0; i < GetLength(branches); i++)
|
|
{
|
|
// Branch has a fruit, save if grown
|
|
if (branches[i].fruit)
|
|
{
|
|
if (!branches[i].fruit->IsGrowing())
|
|
{
|
|
props->AddCall("Branch", this, "GrowBranch", true, i);
|
|
props->AddCall("Fruit", this, "GrowFruit", true);
|
|
}
|
|
else // If fruit is not fully grown, just save the branch
|
|
props->AddCall("Branch", this, "GrowBranch", true, i);
|
|
}
|
|
else if (branches[i].grown) // Save only fully grown branches
|
|
props->AddCall("Branch", this, "GrowBranch", true, i);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Definition */
|
|
|
|
local Name = "$Name$";
|
|
local Description = "$Description$";
|
|
local Collectible = 0;
|
|
local growth = 3;
|
|
local degrowth = -6;
|
|
local fastgrowth = 9;
|