forked from Mirrors/openclonk
525 lines
13 KiB
C
525 lines
13 KiB
C
/**
|
|
Objects.c
|
|
Functions generally applicable to objects; not enough to be worth distinct scripts though.
|
|
|
|
@author Maikel, boni, Ringwaul, Sven2, flgr, Clonkonaut, Günther, Randrian
|
|
*/
|
|
|
|
// Does not set the speed of an object. But you can set two components of the velocity vector with this function.
|
|
// documented in /docs/sdk/script/fn
|
|
global func SetSpeed(int x_dir, int y_dir, int prec)
|
|
{
|
|
SetXDir(x_dir, prec);
|
|
SetYDir(y_dir, prec);
|
|
return;
|
|
}
|
|
|
|
// Returns the speed of an object.
|
|
global func GetSpeed(int prec)
|
|
{
|
|
return Sqrt(GetXDir(prec)**2 + GetYDir(prec)**2);
|
|
}
|
|
|
|
// You can add to the two components of the velocity vector individually with this function.
|
|
global func AddSpeed(int x_dir, int y_dir, int prec)
|
|
{
|
|
SetXDir(GetXDir(prec) + x_dir, prec);
|
|
SetYDir(GetYDir(prec) + y_dir, prec);
|
|
}
|
|
|
|
// Sets an objects's speed and its direction, doesn't it?
|
|
// Can set either speed or angle of velocity, or both
|
|
global func SetVelocity(int angle, int speed, int precAng, int precSpd)
|
|
{
|
|
if (!precSpd) precSpd = 10;
|
|
if (!precAng) precAng = 1;
|
|
speed = speed ?? Distance(0, 0, GetXDir(precSpd), GetYDir(precSpd));
|
|
angle = angle ?? Angle(0, 0, GetXDir(precSpd), GetYDir(precSpd), precAng);
|
|
|
|
var x_dir = Sin(angle, speed, precAng);
|
|
var y_dir = -Cos(angle, speed, precAng);
|
|
|
|
SetXDir(x_dir, precSpd);
|
|
SetYDir(y_dir, precSpd);
|
|
return;
|
|
}
|
|
|
|
// Adds to an objects's speed and its direction:
|
|
// Can set either speed or angle of velocity, or both
|
|
global func AddVelocity(int angle, int speed, int precision_angle, int precision_speed)
|
|
{
|
|
precision_speed = precision_speed ?? 10;
|
|
precision_angle = precision_angle ?? 1;
|
|
speed = speed ?? 0;
|
|
angle = angle ?? 0;
|
|
|
|
var current_x_dir = GetXDir(precision_speed);
|
|
var current_y_dir = GetYDir(precision_speed);
|
|
|
|
var x_dir = +Sin(angle, speed, precision_angle);
|
|
var y_dir = -Cos(angle, speed, precision_angle);
|
|
|
|
SetXDir(current_x_dir + x_dir, precision_speed);
|
|
SetYDir(current_y_dir + y_dir, precision_speed);
|
|
return;
|
|
}
|
|
|
|
|
|
// Sets the completion of this to new_con.
|
|
// documented in /docs/sdk/script/fn
|
|
global func SetCon(int new_con, int precision, bool grow_from_center)
|
|
{
|
|
return DoCon(new_con - GetCon(), precision, grow_from_center);
|
|
}
|
|
|
|
global func GetObjAlpha()
|
|
{
|
|
return (GetClrModulation() >> 24) & 0xFF;
|
|
}
|
|
|
|
// Sets the object's transparency.
|
|
global func SetObjAlpha(int by_alpha)
|
|
{
|
|
var clr_mod = GetClrModulation();
|
|
|
|
if (!clr_mod)
|
|
clr_mod = by_alpha << 24;
|
|
else
|
|
clr_mod = clr_mod & 16777215 | by_alpha << 24;
|
|
return SetClrModulation(clr_mod);
|
|
}
|
|
|
|
global func MakeInvincible(bool allow_fire)
|
|
{
|
|
if (!this) return false;
|
|
var fx = GetEffect("IntInvincible", this);
|
|
if (!fx) fx = AddEffect("IntInvincible", this, 300, 0);
|
|
if (!fx) return false;
|
|
fx.allow_fire = allow_fire;
|
|
if (!allow_fire && this->OnFire()) this->Extinguish();
|
|
fx.OnShockwaveHit = this.OnShockwaveHit;
|
|
fx.RejectWindbagForce = this.RejectWindbagForce;
|
|
fx.QueryCatchBlow = this.QueryCatchBlow;
|
|
this.OnShockwaveHit = Global.Invincibility_OnShockwaveHit;
|
|
this.RejectWindbagForce = Global.Invincibility_RejectWindbagForce;
|
|
this.QueryCatchBlow = Global.Invincibility_QueryCatchBlow;
|
|
return true;
|
|
}
|
|
|
|
global func IsInvincible()
|
|
{
|
|
return !!GetEffect("IntInvincible", this);
|
|
}
|
|
|
|
global func SetInvincibility(bool to_val)
|
|
{
|
|
// Turn invincibility on or off
|
|
if (to_val)
|
|
return MakeInvincible(false);
|
|
else
|
|
return ClearInvincible();
|
|
}
|
|
|
|
global func FxIntInvincibleDamage(target)
|
|
{
|
|
// avert all damage
|
|
return 0;
|
|
}
|
|
|
|
global func FxIntInvincibleEffect(string new_name, object target, proplist fx)
|
|
{
|
|
// Block fire effects.
|
|
if (WildcardMatch(new_name, "*Fire*") && !fx.allow_fire)
|
|
return FX_Effect_Deny;
|
|
// All other effects are okay.
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxIntInvincibleSaveScen(object obj, proplist fx, proplist props)
|
|
{
|
|
// this is invincible. Save to scenario.
|
|
props->AddCall("Invincible", obj, "MakeInvincible", fx.allow_fire);
|
|
return true;
|
|
}
|
|
|
|
// Removes invincibility from object
|
|
global func ClearInvincible()
|
|
{
|
|
if (!this) return nil;
|
|
var fx = GetEffect("IntInvincible", this);
|
|
if (fx)
|
|
{
|
|
this.OnShockwaveHit = fx.OnShockwaveHit;
|
|
this.RejectWindbagForce = fx.RejectWindbagForce;
|
|
this.QueryCatchBlow = fx.QueryCatchBlow;
|
|
} else { // just to be sure
|
|
this.OnShockwaveHit = this->GetID().OnShockwaveHit;
|
|
this.RejectWindbagForce = this->GetID().RejectWindbagForce;
|
|
this.QueryCatchBlow = this->GetID().QueryCatchBlow;
|
|
}
|
|
return RemoveEffect("IntInvincible", this);
|
|
}
|
|
|
|
global func Invincibility_OnShockwaveHit()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
global func Invincibility_RejectWindbagForce()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
global func Invincibility_QueryCatchBlow()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Move an object by the given parameters relative to its position.
|
|
global func MovePosition(int x, int y, int prec)
|
|
{
|
|
SetPosition(GetX(prec) + x, GetY(prec) + y, nil, prec);
|
|
}
|
|
|
|
// Returns the position as an array
|
|
global func GetPosition(int prec)
|
|
{
|
|
return [GetX(prec), GetY(prec)];
|
|
}
|
|
|
|
// Speed the calling object into the given direction (angle)
|
|
global func LaunchProjectile(int angle, int dist, int speed, int x, int y, int precAng, int precSpd, bool rel_x)
|
|
{
|
|
// dist: Distance object travels on angle. Offset from calling object.
|
|
// x: X offset from container's center
|
|
// y: Y offset from container's center
|
|
// rel_x: if true, makes the X offset relative to container direction. (x=+30 will become x=-30 when Clonk turns left. This way offset always stays in front of a Clonk.)
|
|
|
|
var x_offset = x ?? Sin(angle, dist, precAng);
|
|
var y_offset = y ?? -Cos(angle, dist, precAng);
|
|
|
|
if(!precAng) precAng = 1;
|
|
if(!precSpd) precSpd = 10;
|
|
|
|
if (Contained() != nil && rel_x == true)
|
|
if (Contained()->GetDir() == 0)
|
|
x = -x;
|
|
|
|
if (Contained() != nil)
|
|
{
|
|
Exit(x_offset, y_offset, angle / precAng);
|
|
SetVelocity(angle, speed, precAng, precSpd);
|
|
return true;
|
|
}
|
|
|
|
if (Contained() == nil)
|
|
{
|
|
SetPosition(GetX() + x_offset, GetY() + y_offset);
|
|
SetR(angle/precAng);
|
|
SetVelocity(angle, speed, precAng, precSpd);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Sets the MaxEnergy value of an object and does the necessary callbacks.
|
|
global func SetMaxEnergy(int value)
|
|
{
|
|
if (!this)
|
|
return;
|
|
value *= 1000;
|
|
var old_maxenergy = this.MaxEnergy;
|
|
this.MaxEnergy = value;
|
|
// Change current energy percentage wise and implicit callback.
|
|
DoEnergy(GetEnergy() * (value - old_maxenergy) / old_maxenergy);
|
|
return;
|
|
}
|
|
|
|
// Returns the MaxEnergy value of an object.
|
|
global func GetMaxEnergy()
|
|
{
|
|
if (!this)
|
|
return;
|
|
return this.MaxEnergy / 1000;
|
|
}
|
|
|
|
// Returns whether an object is at full energy, that is if its energy is >= its MaxEnergy
|
|
global func HasMaxEnergy()
|
|
{
|
|
if (!this)
|
|
return false;
|
|
return GetEnergy() >= GetMaxEnergy();
|
|
}
|
|
|
|
// Sets the MaxBreath value of an object and does the necessary callbacks.
|
|
global func SetMaxBreath(int value)
|
|
{
|
|
if (!this)
|
|
return;
|
|
var old_maxbreath = this.MaxBreath;
|
|
this.MaxBreath = value;
|
|
// Change current breath percentage wise and implicit callback.
|
|
DoBreath(GetBreath() * (value - old_maxbreath) / old_maxbreath);
|
|
return;
|
|
}
|
|
|
|
// Returns the MaxBreath value of an object.
|
|
global func GetMaxBreath()
|
|
{
|
|
if (!this)
|
|
return;
|
|
return this.MaxBreath;
|
|
}
|
|
|
|
// Makes an object gain Con until it is FullCon.
|
|
// value: the object grows approx. every second, in tenths of percent.
|
|
// max_size = the maximum object size in tenths of percent.
|
|
global func StartGrowth(int value, int max_size)
|
|
{
|
|
if (value <= 0) return nil;
|
|
// Ensure max size is set and does not conflict with Oversize.
|
|
max_size = max_size ?? 1000;
|
|
if (!GetDefCoreVal("Oversize", "DefCore"))
|
|
max_size = Min(max_size, 1000);
|
|
var fx = AddEffect("IntGrowth", this, 1, 35, this, nil, value, max_size);
|
|
fx.Time = Random(35);
|
|
return fx;
|
|
}
|
|
|
|
global func StopGrowth()
|
|
{
|
|
return RemoveEffect("IntGrowth", this);
|
|
}
|
|
|
|
global func GetGrowthValue()
|
|
{
|
|
var fx = GetEffect("IntGrowth", this);
|
|
if (!fx)
|
|
return 0;
|
|
return fx.growth;
|
|
}
|
|
|
|
global func GetGrowthMaxSize()
|
|
{
|
|
var fx = GetEffect("IntGrowth", this);
|
|
if (!fx)
|
|
return 0;
|
|
return fx.max_size;
|
|
}
|
|
|
|
global func FxIntGrowthStart(object obj, effect fx, int temporary, int value, int max_size)
|
|
{
|
|
if (!temporary)
|
|
{
|
|
fx.growth = value;
|
|
fx.max_size = max_size;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxIntGrowthTimer(object obj, effect fx)
|
|
{
|
|
if (obj->OnFire())
|
|
return FX_OK;
|
|
obj->DoCon(fx.growth, 1000);
|
|
// Negative growth might have removed the object.
|
|
if (!obj)
|
|
return FX_Execute_Kill;
|
|
var done = obj->GetCon(1000) >= fx.max_size;
|
|
return -done;
|
|
}
|
|
|
|
// Plays hit sounds for an average object made of stone or stone-like material.
|
|
// x and y need to be the parameters passed to Hit() from the engine.
|
|
global func StonyObjectHit(int x, int y)
|
|
{
|
|
// Failsafe
|
|
if (!this) return false;
|
|
var xdir = GetXDir(), ydir = GetYDir();
|
|
if (x) x = x / Abs(x);
|
|
if (y) y = y / Abs(y);
|
|
// Check for solid in hit direction
|
|
var i = 0;
|
|
var average_obj_size = Distance(0,0, GetObjWidth(), GetObjHeight()) / 2 + 2;
|
|
while (!GBackSolid(x * i, y * i) && i < average_obj_size)
|
|
i++;
|
|
// To catch some high speed cases: if no solid found, check directly beneath
|
|
if (!GBackSolid(x * i, y * i))
|
|
while (!GBackSolid(x * i, y * i) && i < average_obj_size)
|
|
i++;
|
|
// Check if digfree
|
|
if (!GetMaterialVal("DigFree", "Material", GetMaterial(x*i, y*i)) && GBackSolid(x*i, y*i))
|
|
return Sound("Hits::Materials::Rock::RockHit?");
|
|
// Else play standard sound
|
|
if (Distance(0,0,xdir,ydir) > 10)
|
|
return Sound("Hits::SoftTouch?");
|
|
else
|
|
return Sound("Hits::SoftHit?");
|
|
}
|
|
|
|
// Removes all objects of the given type.
|
|
// documented in /docs/sdk/script/fn
|
|
global func RemoveAll(p, ...)
|
|
{
|
|
var cnt;
|
|
if (GetType(p) == C4V_PropList) p = Find_ID(p); // RemoveAll(ID) shortcut
|
|
for (var obj in FindObjects(p, ...))
|
|
{
|
|
if (obj)
|
|
{
|
|
obj->RemoveObject();
|
|
cnt++;
|
|
}
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
// Pulls an object above ground if it was buried (e.g. by PlaceVegetation), mainly used by plants.
|
|
// The object must have 'Bottom' and 'Center' CNAT to use this.
|
|
// (bottom is the point which should be buried, center the lowest point that must not be buried)
|
|
global func RootSurface(int max_movement)
|
|
{
|
|
max_movement = max_movement ?? GetObjHeight() / 2;
|
|
|
|
if (HasCNAT(CNAT_Center))
|
|
{
|
|
var i = 0;
|
|
// Move up if too far underground.
|
|
while (GetContact(-1) & CNAT_Center && i < max_movement)
|
|
{
|
|
SetPosition(GetX(), GetY() - 1);
|
|
i++;
|
|
}
|
|
}
|
|
if (HasCNAT(CNAT_Bottom))
|
|
{
|
|
var i = 0;
|
|
// Move down if in midair.
|
|
while (!(GetContact(-1) & CNAT_Bottom) && i < max_movement)
|
|
{
|
|
SetPosition(GetX(), GetY() + 1);
|
|
i++;
|
|
}
|
|
// Try to make the plant stuck.
|
|
if (!Stuck())
|
|
SetPosition(GetX(), GetY() + 1);
|
|
}
|
|
}
|
|
|
|
// Buys an object. Returns the object if it could be bought.
|
|
// documented in /docs/sdk/script/fn
|
|
global func Buy(id buy_def, int for_plr, int pay_plr, object from_vendor, bool show_errors)
|
|
{
|
|
// if no vendor is given try this
|
|
if (!from_vendor)
|
|
from_vendor = this;
|
|
// not a vendor?
|
|
if (!from_vendor->~IsVendor())
|
|
return nil;
|
|
return from_vendor->DoBuy(buy_def, for_plr, pay_plr, nil, false, show_errors);
|
|
}
|
|
|
|
// Sells an object. Returns true if it could be sold.
|
|
// documented in /docs/sdk/script/fn
|
|
global func Sell(int plr, object obj, object to_vendor)
|
|
{
|
|
// if no vendor is given try this
|
|
if (!to_vendor)
|
|
to_vendor = this;
|
|
// not a vendor?
|
|
if (!to_vendor->~IsVendor())
|
|
return false;
|
|
return to_vendor->DoSell(obj, plr);
|
|
}
|
|
|
|
// GetXEdge returns the position of the objects top/bottom/left/right edge.
|
|
global func GetLeftEdge()
|
|
{
|
|
return GetX() - GetObjWidth() / 2;
|
|
}
|
|
|
|
global func GetRightEdge()
|
|
{
|
|
return GetX() + GetObjWidth() / 2;
|
|
}
|
|
|
|
global func GetTopEdge()
|
|
{
|
|
return GetY() - GetObjHeight() / 2;
|
|
}
|
|
|
|
global func GetBottomEdge()
|
|
{
|
|
return GetY() + GetObjHeight() / 2;
|
|
}
|
|
|
|
// Returns if the object is standing in front of the back-object
|
|
global func InFrontOf(object back)
|
|
{
|
|
var front = this;
|
|
if (!front)
|
|
return;
|
|
return front->FindObject(front->Find_AtPoint(), Find_Not(Find_Exclude(back))) != nil;
|
|
}
|
|
|
|
// Returns the current left of the object relative to its current position.
|
|
global func GetLeft()
|
|
{
|
|
var offset_x = GetObjectVal("Offset", nil, 0);
|
|
if (offset_x == nil)
|
|
offset_x = GetDefCoreVal("Offset", nil, 0);
|
|
return offset_x;
|
|
}
|
|
|
|
// Returns the current right of the object relative to its current position.
|
|
global func GetRight()
|
|
{
|
|
var offset_x = GetObjectVal("Offset", nil, 0);
|
|
if (offset_x == nil)
|
|
offset_x = GetDefCoreVal("Offset", nil, 0);
|
|
var width = GetObjectVal("Width");
|
|
return offset_x + width;
|
|
}
|
|
|
|
// Returns the current top of the object relative to its current position.
|
|
global func GetTop()
|
|
{
|
|
var offset_y = GetObjectVal("Offset", nil, 1);
|
|
if (offset_y == nil)
|
|
offset_y = GetDefCoreVal("Offset", nil, 1);
|
|
return offset_y;
|
|
}
|
|
|
|
// Returns the current bottom of the object relative to its current position.
|
|
global func GetBottom()
|
|
{
|
|
var offset_y = GetObjectVal("Offset", nil, 1);
|
|
if (offset_y == nil)
|
|
offset_y = GetDefCoreVal("Offset", nil, 1);
|
|
var height = GetObjectVal("Height");
|
|
return offset_y + height;
|
|
}
|
|
|
|
// Returns the current shape as an array [offset_x, offset_y, width, height].
|
|
global func GetShape()
|
|
{
|
|
var offset_x = GetObjectVal("Offset", nil, 0);
|
|
if (offset_x == nil)
|
|
offset_x= GetDefCoreVal("Offset", nil, 0);
|
|
var offset_y = GetObjectVal("Offset", nil, 1);
|
|
if (offset_y == nil)
|
|
offset_y = GetDefCoreVal("Offset", nil, 1);
|
|
var width = GetObjectVal("Width");
|
|
var height = GetObjectVal("Height");
|
|
return [offset_x, offset_y, width, height];
|
|
}
|
|
|
|
global func GetEntranceRectangle()
|
|
{
|
|
var entrance_x = GetDefCoreVal("Entrance", "DefCore", 0);
|
|
var entrance_y = GetDefCoreVal("Entrance", "DefCore", 1);
|
|
var entrance_wdt = GetDefCoreVal("Entrance", "DefCore", 2);
|
|
var entrance_hgt = GetDefCoreVal("Entrance", "DefCore", 3);
|
|
return [entrance_x, entrance_y, entrance_wdt, entrance_hgt];
|
|
}
|