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

475 lines
11 KiB
C

/**
Power
Cares about power management of a base.
callbacks:
QueryWaivePowerRequest()
OnNotEnoughPower()
OnEnoughPower()
OnRemovedFromPowerSleepingQueue(): called when the object was removed from the sleeping queue
globals:
MakePowerConsumer(int amount)
Note: power consumers include the library Library_PowerConsumer and should use UnmakePowerConsumer to turn off as power consumers
MakePowerProducer(int amount)
IsPowerAvailable(int amount)
*/
static Library_Power_power_compounds;
// for the helper definitions
local power_links; // producers and consumers
local sleeping_links;
local power_balance; // performance
local neutral; // is "the" neutral helper?
func Initialize()
{
power_links = [];
sleeping_links = [];
power_balance = 0;
neutral = false;
}
func AddPowerProducer(object p, int a)
{
return AddPowerLink(p, a);
}
func AddPowerConsumer(object p, int a)
{
// possibly sleeping?
{
for(var i = GetLength(sleeping_links); --i >= 0;)
{
var o = sleeping_links[i];
if(o.obj != p) continue;
// did not affect power balance, we can just remove/change the link
if(a == 0) // remove
{
sleeping_links[i] = sleeping_links[GetLength(sleeping_links) - 1];
SetLength(sleeping_links, GetLength(sleeping_links) - 1);
// message
var diff = 0;
{
VisualizePowerChange(o.obj, 0, o.amount, false);
}
o.obj->~OnRemovedFromPowerSleepingQueue();
return true;
}
sleeping_links[i].amount = -a;
return true;
}
}
// not asleep
return AddPowerLink(p, -a);
}
func RemovePowerLink(object p)
{
return AddPowerLink(p, 0);
}
func AddPowerLink(object p, int a, bool surpress_balance_check)
{
var n = {obj = p, amount = a};
var before = 0;
var found = false;
var first_empty = -1;
var diff = 0;
for(var i = GetLength(power_links); --i >= 0;)
{
var o = power_links[i];
if(o == nil) // possible
{
first_empty = i;
continue;
}
if(o.obj == nil) // removed from outside
{
power_links[i] = nil;
continue;
}
if(o.obj != p) continue;
found = true;
diff = a - o.amount;
power_balance += diff;
before = power_links[i].amount;
if(a == 0)
{
power_links[i] = nil;
}
else power_links[i] = n;
break;
}
if(!found)
{
// place to insert?
if(a != 0)
{
if(first_empty != -1)
power_links[first_empty] = n;
else
power_links[GetLength(power_links)] = n;
}
diff = n.amount;
power_balance += diff;
}
if((n.amount > 0) || ((n.amount == 0) && (before > 0)))
{
VisualizePowerChange(n.obj, n.amount, before, false);
}
else if((n.amount < 0) || ((n.amount == 0) && (before < 0)))
{
VisualizePowerChange(n.obj, n.amount, before, false);
}
if(n.amount < 0)
n.obj->~OnEnoughPower(); // might be reverted soon, though
if(!surpress_balance_check)
CheckPowerBalance();
return true;
}
func CheckPowerBalance()
{
// special handling for ownerless links
// always sleep
if(neutral)
{
for(var i = GetLength(power_links); --i >= 0;)
{
var o = power_links[i];
if(o == nil) continue;
if(o.amount > 0) continue; // producer
SleepLink(i);
}
return false;
}
//Message("@Power: %d", power_balance);
if(power_balance >= 0) // alrighty
{
// we are good to go
// we could revive sleeping links at this point
if(GetLength(sleeping_links))
{
for(var i = GetLength(sleeping_links); --i >= 0;)
{
var o = sleeping_links[i];
if(o.obj == nil)
{
sleeping_links[i] = sleeping_links[GetLength(sleeping_links) - 1];
SetLength(sleeping_links, GetLength(sleeping_links) - 1);
continue;
}
if(power_balance + o.amount < 0) continue;
// found link to revive!
UnsleepLink(i);
// can not continue since UnsleepLink changes the array
return true;
}
}
return true;
}
// something happened
// something evil
// look for consumer to kick out of system
// best-fit strategy (or volunteers)
var best_fit = 0xFFFFFF;
var best_volunteer = 0;
var best = -1;
var abs_diff = Abs(power_balance);
for(var i = GetLength(power_links); --i >= 0;)
{
var o = power_links[i];
if(o == nil) continue;
if(o.amount > 0) continue; // producer
var d = Abs(((-o.amount) - abs_diff));
var v = o.obj->~QueryWaivePowerRequest();
if(!best_volunteer) // no volunteers yet
{
if((d < best_fit) || (best == nil) || (v > 0))
{
best_fit = d;
best = i;
if(v)
best_volunteer = v;
}
}
else // we already have volunteers
{
if(v < best_volunteer) continue;
best_volunteer = v;
best = i;
}
}
// total blackout? No consumer active anymore?
if(best == -1)
{
return false;
}
// has object
SleepLink(best);
// recurse
// might revive weaker consumer or sleep another one
CheckPowerBalance();
return false;
}
func SleepLink(int index)
{
if(index < 0 || index >= GetLength(power_links))
return FatalError("SleepLink() called without valid index!");
// only consumers, sleeping producers does not make sense...
var o = power_links[index];
if(o.amount > 0) return FatalError("SleepLink() trying to sleep producer!");
// delete from list
power_links[index] = nil;
power_balance -= o.amount;
sleeping_links[GetLength(sleeping_links)] = o;
// sadly not enough power anymore
o.obj->~OnNotEnoughPower();
VisualizePowerChange(o.obj, 0, o.amount, true);
return true;
}
func UnsleepLink(int index)
{
if(index < 0 || index >= GetLength(sleeping_links))
return FatalError("UnsleepLink() called without valid index!");
var o = sleeping_links[index];
// delete
sleeping_links[index] = sleeping_links[GetLength(sleeping_links) - 1];
SetLength(sleeping_links, GetLength(sleeping_links) - 1);
return AddPowerLink(o.obj, o.amount); // revives the link
}
// get requested power of nodes that are currently sleeping
public func GetPendingPowerAmount()
{
var sum = 0;
for(var i = GetLength(sleeping_links); --i >= 0;)
{
sum += -sleeping_links[i].amount;
}
return sum;
}
// should always be above zero - otherwise an object would have been deactivated
public func GetPowerBalance()
{
return power_balance;
}
public func IsPowerAvailable(object obj, int amount)
{
// ignore object for now
return power_balance > amount;
}
public func Init()
{
if(GetType(Library_Power_power_compounds) != C4V_Array)
Library_Power_power_compounds = [];
}
// static
func VisualizePowerChange(object obj, int to, int before, bool loss)
{
var before_current = nil;
var e = GetEffect("VisualPowerChange", obj);
if(!e)
e = AddEffect("VisualPowerChange", obj, 1, 5, nil, Library_Power);
else before_current = e.current;
var to_abs = Abs(to);
var before_abs = Abs(before);
e.max = Max(to_abs, before_abs);
e.current = before_current ?? before_abs;
e.to = to_abs;
if(loss)
e.back_graphics_name = "Red";
else e.back_graphics_name = nil;
if(to < 0) e.graphics_name = "Yellow";
else if(to > 0) e.graphics_name = "Green";
else // off now
{
if(before < 0) e.graphics_name = "Yellow";
else e.graphics_name = "Green";
}
EffectCall(obj, e, "Refresh");
}
func FxVisualPowerChangeRefresh(target, effect)
{
if(effect.bar) effect.bar->Close();
var vis = VIS_Allies | VIS_Owner;
var controller = target->GetController();
if(controller == NO_OWNER) vis = VIS_All;
var off_x = -(target->GetDefCoreVal("Width", "DefCore") * 3) / 8;
var off_y = target->GetDefCoreVal("Height", "DefCore") / 2 - 10;
effect.bar = target->CreateProgressBar(GUI_BarProgressBar, effect.max, effect.current, 35
, controller, {x = off_x, y = off_y}, vis
, {size = 1000, bars = effect.max / 25, graphics_name = effect.graphics_name, back_graphics_name = effect.back_graphics_name, image = Icon_Lightbulb, fade_speed = 1});
// appear on a GUI level in front of other objects (f.e. trees)
effect.bar->SetPlane(1010);
}
func FxVisualPowerChangeTimer(target, effect, time)
{
if(!effect.bar) return -1;
if(effect.current == effect.to) return 1;
if(effect.to < effect.current) effect.current = Max(effect.current - 15, effect.to);
else effect.current = Min(effect.current + 15, effect.to);
effect.bar->SetValue(effect.current);
return 1;
}
// static
func GetPowerHelperForObject(object who)
{
var w;
while(w = who->~GetActualPowerConsumer())
{
if(w == who) break; // nope
who = w;
}
var flag = GetFlagpoleForPosition(who->GetX() - GetX(), who->GetY() - GetY());
var helper = nil;
if(!flag) // neutral - needs neutral helper
{
for(var obj in Library_Power_power_compounds)
{
if(!obj.neutral) continue;
helper = obj;
break;
}
if(helper == nil) // not yet created?
{
helper = CreateObject(Library_Power, 0, 0, NO_OWNER);
helper.neutral = true;
Library_Power_power_compounds[GetLength(Library_Power_power_compounds)] = helper;
}
}
else
{
helper=flag->GetPowerHelper();
if(helper == nil)
{
helper = CreateObject(Library_Power, 0, 0, NO_OWNER);
Library_Power_power_compounds[GetLength(Library_Power_power_compounds)] = helper;
// add to all linked flags
flag->SetPowerHelper(helper);
for(var f in flag->GetLinkedFlags())
{
if(f->GetPowerHelper() != nil) // assert
FatalError("Flags in compound have different power helper!");
f->SetPowerHelper(helper);
}
}
}
return helper;
}
// returns the amount of unavailable power that is currently being request
global func GetPendingPowerAmount()
{
if(!this) return 0;
Library_Power->Init();
return (Library_Power->GetPowerHelperForObject(this))->GetPendingPowerAmount();
}
// returns the current power balance of the area an object is in.
// this is roughly equivalent to produced_power - consumed_power
global func GetCurrentPowerBalance()
{
if(!this) return 0;
Library_Power->Init();
return (Library_Power->GetPowerHelperForObject(this))->GetPowerBalance();
}
// turns the object into a power producer that produces /amount/ power until the function is called again with amount = 0
global func MakePowerProducer(int amount /* the amount of power to produce constantly, 0 to turn off */)
{
if(!this) return false;
Library_Power->Init();
return (Library_Power->GetPowerHelperForObject(this))->AddPowerProducer(this, amount);
}
/** Turns the power producer into an object that does not produce power */
global func UnmakePowerProducer()
{
MakePowerProducer(0);
}
// returns true if the current power balance is bigger or equal amount
global func IsPowerAvailable(int amount)
{
if(!this) return false;
Library_Power->Init();
return (Library_Power->GetPowerHelperForObject(this))->IsPowerAvailable(this, amount);
}
// turns the object into a power consumer
global func MakePowerConsumer(int amount /* the amount of power to request, 0 to turn off */)
{
if(!this) return false;
Library_Power->Init();
return (Library_Power->GetPowerHelperForObject(this))->AddPowerConsumer(this, amount);
}
// helper object