openclonk/planet/Objects.ocd/Libraries.ocd/Destructible.ocd/Script.c

193 lines
5.3 KiB
C

/**
Library_Destructible
Contains the logic for any object/vehicle that is destructible, mainly with explosions.
But can also be applied to flammable objects.
Objects will break apart after the damage exceeds local HitPoints (before that OnMaxDamage() is called).
After half the hitpoints are gone, any mesh model will change it's texture to *_Damaged (same material name).
OnHalfDamage is called, to e.g. change more submesh textures.
When repaired over half the amount of hitpoints, OnHalfDamageReverse is called.
@author Clonkonaut
*/
// Override!
local HitPoints = 0;
// Don't override!
local lib_destructible_is_damaged = false;
func Damage(int change, int cause, int by_player)
{
_inherited(change, cause, by_player, ...);
// You shouldn't remove the object in Damage when using this library, but better check
if (!this) return;
// Damaged
if (!lib_destructible_is_damaged && GetDamage() >= this.HitPoints / 2)
{
// Become damaged
this->~OnHalfDamage(change, cause, by_player);
lib_destructible_is_damaged = true;
if (GetMeshMaterial())
{
var mat_name = GetMeshMaterial();
mat_name = Format("%s_Damaged", mat_name);
SetMeshMaterial(mat_name);
}
}
// Repaired
if (lib_destructible_is_damaged && GetDamage() <= this.HitPoints / 2)
{
// Become undamaged
this->~OnHalfDamageReverse(change, cause, by_player);
lib_destructible_is_damaged = false;
if (GetMeshMaterial())
{
var mat_name = GetMeshMaterial();
mat_name = TakeString(mat_name, 0, GetLength(mat_name) - 8);
SetMeshMaterial(mat_name);
}
}
// Destroyed
if (GetDamage() >= this.HitPoints)
{
// Only by explosions? Check!
if (IsDestroyedByExplosions())
if (cause != FX_Call_DmgBlast)
return;
if (this->~OnDestruction(change, cause, by_player)) // Custom effects
return;
// First eject the contents in different directions.
for (var obj in FindObjects(Find_Container(this)))
{
var speed = RandomX(3, 5);
var angle = Random(360);
var dx = Cos(angle, speed);
var dy = Sin(angle, speed);
obj->Exit(RandomX(-4, 4), RandomX(-4, 4), Random(360), dx, dy, RandomX(-20, 20));
obj->SetController(by_player);
}
// Toss around some fragments with particles attached.
for (var i = 0; i < 6; i++)
{
var fragment = CreateObject(DestructibleFragment, RandomX(-4, 4), RandomX(-4, 4), GetOwner());
var speed = RandomX(40, 60);
var angle = Random(360);
var dx = Cos(angle, speed);
var dy = Sin(angle, speed);
fragment->SetXDir(dx, 10);
fragment->SetYDir(dy, 10);
fragment->SetR(360);
fragment->SetRDir(RandomX(-20, 20));
// Set the controller of the fragments to the one causing the blast for kill tracing.
fragment->SetController(by_player);
// Incinerate the fragments.
fragment->Incinerate(100, by_player);
}
// Remove
RemoveObject(true);
}
}
// Default behaviour: only explosion damage will cause the object to be destroyed
// But any kind of damage will trigger a change of texture etc.
public func IsDestroyedByExplosions() { return true; }
/*-- Interaction Menu --*/
// Always show an interaction menu with at least the damage entry.
public func HasInteractionMenu() { return true; }
public func RejectInteractionMenu(object clonk)
{
if (GetCon() < 100)
return Format("$MsgNotFullyConstructed$", GetName());
return _inherited(clonk, ...);
}
// Show damage in the interaction menu.
public func GetInteractionMenus(object clonk)
{
var menus = _inherited(clonk, ...) ?? [];
var damage_menu =
{
title = "$Damage$",
entries_callback = this.GetDamageMenuEntries,
entries_callback_target = this,
callback_hover = "OnDamageMenuHover",
callback_target = this,
BackgroundColor = RGB(75, 50, 0),
Priority = 90
};
PushBack(menus, damage_menu);
return menus;
}
// Returns the contents of the "damage" section in the interaction menu.
public func GetDamageMenuEntries()
{
var is_invincible = this.HitPoints == nil || this->IsInvincible();
var damage_text = "$Invincible$";
var color = RGB(0, 150, 0);
if (!is_invincible)
{
if (GetDamage() == 0)
damage_text = "$NotDamaged$";
else if (GetDamage() < this.HitPoints / 2)
{
damage_text = "$SlightlyDamaged$";
color = RGB(200, 150, 0);
}
else
{
damage_text = "<c ff0000>$HeavilyDamaged$</c>";
color = RGB(150, 0, 0);
}
}
var menu =
{
Bottom = "2em",
bar =
{
Left = "0.2em",
Right = "100% - 0.2em",
bottom = {Top = "50%", Margin = "0.1em", BackgroundColor = RGB(0, 0, 0)},
top = {Text = damage_text, Style = GUI_TextHCenter}
}
};
// Show hit points.
var percent = "100%";
if (!is_invincible)
percent = Format("%d%%", 100 * (this.HitPoints - GetDamage()) / this.HitPoints);
menu.bar.bottom.fill = {BackgroundColor = color, Right = percent, Margin = "0.1em"};
return [{symbol = this, extra_data = "repair", custom = menu}];
}
// On hovering, show a list of materials that are needed for repairing the structure.
public func OnDamageMenuHover(id symbol, string action, desc_menu_target, menu_id)
{
var is_invincible = this.HitPoints == nil || this->IsInvincible();
var damage_text = "$Invincible$";
if (!is_invincible)
{
if (GetDamage() == 0)
damage_text = "$NotDamaged$";
else if (GetDamage() < this.HitPoints / 2)
damage_text = "$SlightlyDamaged$";
else
damage_text = "<c ff0000>$HeavilyDamaged$</c>";
}
GuiUpdateText(damage_text, menu_id, 1, desc_menu_target);
}