387 lines
9.0 KiB
ActionScript
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();
|
|
}
|