Files
starruler-linux/scripts/server/empire_ai/weasel/Budget.as
T
2018-07-17 14:15:37 +02:00

387 lines
9.0 KiB
ActionScript

// Budget
// ------
// Tasked with managing the empire's money and making sure we have enough to spend
// on various things, as well as dealing with prioritization and budget allocation.
//
import empire_ai.weasel.WeaselAI;
enum BudgetType {
BT_Military,
BT_Colonization,
BT_Development,
BT_COUNT
};
final class AllocateBudget {
int id = -1;
uint type;
int cost = 0;
int maintenance = 0;
double requestTime = 0;
double priority = 1;
bool allocated = false;
int opCmp(const AllocateBudget@ other) const {
if(priority < other.priority)
return -1;
if(priority > other.priority)
return 1;
if(requestTime < other.requestTime)
return 1;
if(requestTime > other.requestTime)
return -1;
return 0;
}
void save(SaveFile& file) {
file << id;
file << type;
file << cost;
file << maintenance;
file << requestTime;
file << priority;
file << allocated;
}
void load(SaveFile& file) {
file >> id;
file >> type;
file >> cost;
file >> maintenance;
file >> requestTime;
file >> priority;
file >> allocated;
}
};
final class BudgetPart {
uint type;
array<AllocateBudget@> allocations;
//How much we've spent this cycle
int spent = 0;
//How much is remaining to be spent this cycle
int remaining = 0;
//How much maintenance we've gained this cycle
int gainedMaintenance = 0;
//How much maintenance we can still gain this cycle
int remainingMaintenance = 0;
void update(AI& ai, Budget& budget) {
for(uint i = 0, cnt = allocations.length; i < cnt; ++i) {
auto@ alloc = allocations[i];
if(alloc.priority < 1.0) {
if(alloc.cost >= remaining && alloc.maintenance >= remainingMaintenance) {
budget.spend(type, alloc.cost, alloc.maintenance);
alloc.allocated = true;
allocations.removeAt(i);
break;
}
}
else {
if(budget.canSpend(type, alloc.cost, alloc.maintenance)) {
budget.spend(type, alloc.cost, alloc.maintenance);
alloc.allocated = true;
allocations.removeAt(i);
break;
}
}
}
}
void turn(AI& ai, Budget& budget) {
spent = 0;
remaining = 0;
gainedMaintenance = 0;
remainingMaintenance = 0;
}
void save(Budget& budget, SaveFile& file) {
file << spent;
file << remaining;
file << gainedMaintenance;
file << remainingMaintenance;
uint cnt = allocations.length;
file << cnt;
for(uint i = 0; i < cnt; ++i) {
budget.saveAlloc(file, allocations[i]);
allocations[i].save(file);
}
}
void load(Budget& budget, SaveFile& file) {
file >> spent;
file >> remaining;
file >> gainedMaintenance;
file >> remainingMaintenance;
uint cnt = 0;
file >> cnt;
for(uint i = 0; i < cnt; ++i) {
auto@ alloc = budget.loadAlloc(file);
alloc.load(file);
}
}
};
final class Budget : AIComponent {
array<BudgetPart@> parts;
int NextAllocId = 0;
int InitialBudget = 0;
int InitialUpcoming = 0;
double Progress = 0;
double RemainingTime = 0;
int FreeBudget = 0;
int FreeMaintenance = 0;
bool checkedMilitarySpending = false;
void create() {
parts.length = BT_COUNT;
for(uint i = 0; i < BT_COUNT; ++i) {
@parts[i] = BudgetPart();
parts[i].type = BudgetType(i);
}
}
void save(SaveFile& file) {
file << InitialBudget;
file << InitialUpcoming;
file << Progress;
file << RemainingTime;
file << FreeBudget;
file << FreeMaintenance;
file << NextAllocId;
file << checkedMilitarySpending;
for(uint i = 0, cnt = parts.length; i < cnt; ++i)
parts[i].save(this, file);
}
void load(SaveFile& file) {
file >> InitialBudget;
file >> InitialUpcoming;
file >> Progress;
file >> RemainingTime;
file >> FreeBudget;
file >> FreeMaintenance;
file >> NextAllocId;
file >> checkedMilitarySpending;
for(uint i = 0, cnt = parts.length; i < cnt; ++i)
parts[i].load(this, file);
}
array<AllocateBudget@> loadIds;
AllocateBudget@ loadAlloc(int id) {
if(id == -1)
return null;
for(uint i = 0, cnt = loadIds.length; i < cnt; ++i) {
if(loadIds[i].id == id)
return loadIds[i];
}
AllocateBudget alloc;
alloc.id = id;
loadIds.insertLast(alloc);
return alloc;
}
AllocateBudget@ loadAlloc(SaveFile& file) {
int id = -1;
file >> id;
if(id == -1)
return null;
else
return loadAlloc(id);
}
void saveAlloc(SaveFile& file, AllocateBudget@ alloc) {
int id = -1;
if(alloc !is null)
id = alloc.id;
file << id;
}
void postLoad(AI& ai) {
loadIds.length = 0;
}
void spend(uint type, int money, int maint = 0) {
auto@ part = parts[type];
part.spent += money;
part.gainedMaintenance += maint;
if(part.remaining >= money) {
part.remaining -= money;
}
else if(part.remaining >= 0) {
money -= part.remaining;
FreeBudget -= money;
part.remaining = 0;
}
else {
FreeBudget -= money;
}
if(part.remainingMaintenance >= maint) {
part.remainingMaintenance -= maint;
}
else if(part.remainingMaintenance >= 0) {
maint -= part.remainingMaintenance;
FreeMaintenance -= maint;
part.remainingMaintenance = 0;
}
else {
FreeMaintenance -= money;
}
}
bool canSpend(uint type, int money, int maint = 0) {
int canFree = FreeBudget;
int canFreeMaint = FreeMaintenance;
//Don't allow generic spending until we've checked if we need to spend on military this cycle
if(type == BT_Development && !checkedMilitarySpending && Progress < 0.33)
canFree = 0;
if(type == BT_Colonization)
canFree += 160;
auto@ part = parts[type];
if(money > part.remaining + canFree)
return false;
if(maint != 0 && maint > part.remainingMaintenance + canFreeMaint)
return false;
return true;
}
int spendable(uint type) {
return FreeBudget + parts[type].remaining;
}
int maintainable(uint type) {
return FreeMaintenance + parts[type].remainingMaintenance;
}
void claim(uint type, int money, int maint = 0) {
auto@ part = parts[type];
FreeBudget -= money;
part.remaining += money;
FreeMaintenance -= maint;
part.remainingMaintenance += maint;
}
void turn() {
if(log && gameTime > 10.0) {
ai.print("==============");
ai.print("Unspent:");
ai.print(" Military: "+parts[BT_Military].remaining+" / "+parts[BT_Military].remainingMaintenance);
ai.print(" Colonization: "+parts[BT_Colonization].remaining+" / "+parts[BT_Colonization].remainingMaintenance);
ai.print(" Development: "+parts[BT_Development].remaining+" / "+parts[BT_Development].remainingMaintenance);
ai.print(" FREE: "+FreeBudget+" / "+FreeMaintenance);
ai.print("==============");
ai.print("Total Expenditures:");
ai.print(" Military: "+parts[BT_Military].spent+" / "+parts[BT_Military].gainedMaintenance);
ai.print(" Colonization: "+parts[BT_Colonization].spent+" / "+parts[BT_Colonization].gainedMaintenance);
ai.print(" Development: "+parts[BT_Development].spent+" / "+parts[BT_Development].gainedMaintenance);
ai.print("==============");
}
//Collect some data about this turn
InitialBudget = ai.empire.RemainingBudget;
InitialUpcoming = ai.empire.EstNextBudget;
FreeBudget = InitialBudget;
FreeMaintenance = InitialUpcoming;
checkedMilitarySpending = false;
//Tell the budget parts to perform turns
for(uint i = 0, cnt = parts.length; i < cnt; ++i)
parts[i].turn(ai, this);
}
void remove(AllocateBudget@ alloc) {
if(alloc is null)
return;
if(alloc.allocated) {
FreeBudget += alloc.cost;
FreeMaintenance += alloc.maintenance;
}
parts[alloc.type].allocations.remove(alloc);
}
AllocateBudget@ allocate(uint type, int cost, int maint = 0, double priority = 1.0) {
AllocateBudget alloc;
alloc.id = NextAllocId++;
alloc.type = type;
alloc.cost = cost;
alloc.maintenance = maint;
alloc.priority = priority;
return allocate(alloc);
}
AllocateBudget@ allocate(AllocateBudget@ allocation) {
allocation.requestTime = gameTime;
parts[allocation.type].allocations.insertLast(allocation);
parts[allocation.type].allocations.sortDesc();
return allocation;
}
void applyNow(AllocateBudget@ alloc) {
auto@ part = parts[alloc.type];
spend(alloc.type, alloc.cost, alloc.maintenance);
alloc.allocated = true;
part.allocations.remove(alloc);
}
void grantBonus(int cost, int maint = 0) {
//Spread some bonus budget across all the different parts
FreeBudget += cost;
FreeMaintenance += maint;
}
void tick(double time) {
//Record some simple data
Progress = ai.empire.BudgetTimer / ai.empire.BudgetCycle;
RemainingTime = ai.empire.BudgetCycle - ai.empire.BudgetTimer;
//Update one of the budget parts
for(uint i = 0, cnt = parts.length; i < cnt; ++i) {
auto@ part = parts[i];
part.update(ai, this);
}
}
void focusTick(double time) {
//Detect any extra budget we need to use
int ExpectBudget = FreeBudget;
int ExpectMaint = FreeMaintenance;
for(uint i = 0, cnt = parts.length; i < cnt; ++i) {
ExpectBudget += parts[i].remaining;
ExpectMaint += parts[i].remainingMaintenance;
}
int HaveBudget = ai.empire.RemainingBudget;
int HaveMaint = ai.empire.EstNextBudget;
if(ExpectBudget != HaveBudget || ExpectMaint != HaveMaint)
grantBonus(HaveBudget - ExpectBudget, max(0, HaveMaint - ExpectMaint));
}
};
AIComponent@ createBudget() {
return Budget();
}