forked from Mirrors/openclonk
194 lines
5.4 KiB
C
194 lines
5.4 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);
|
|
// First set the controller, because on exit the object may be removed.
|
|
obj->SetController(by_player);
|
|
obj->Exit(RandomX(-4, 4), RandomX(-4, 4), Random(360), dx, dy, RandomX(-20, 20));
|
|
}
|
|
|
|
// 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);
|
|
}
|