forked from Mirrors/openclonk
475 lines
11 KiB
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
|