Files
bee-reward/main.nut
2014-12-28 17:54:52 +01:00

281 lines
11 KiB
Plaintext

require("company.nut");
class FMainClass extends GSController
{
cargoes = null; // Cargoes of the game (index -> 'cid' number, 'freight' boolean, 'effect' on town).
num_cargoes = 0;
companies = null;
function Start();
}
// Examine and store cargo types of the game.
function FMainClass::ExamineCargoes()
{
this.cargoes = {};
this.num_cargoes = 0;
for (local cid = 0; cid < 32; cid += 1) {
if (!GSCargo.IsValidCargo(cid)) continue;
local is_freight = GSCargo.IsFreight(cid);
local town_effect = GSCargo.GetTownEffect(cid);
this.cargoes[this.num_cargoes] <- {cid=cid, freight=is_freight, effect=town_effect};
this.num_cargoes += 1;
}
}
// Find cargo sources.
// @param cargo_id Cargo index (index in this.cargoes).
// @return List of resources that produce the requested cargo, list of
// 'ind' or 'town' number, 'prod' produced amount, 'transp' transported amount, and 'loc' location.
function FMainClass::FindSources(cargo_id)
{
local cargo = this.cargoes[cargo_id];
local num_sources = 0;
local sources = {};
if (cargo.freight) {
// For 'freight' cargoes, check the producing industries for sufficient available production.
foreach (ind, _ in GSIndustryList_CargoProducing(cargo.cid)) {
local prod_amount = GSIndustry.GetLastMonthProduction(ind, cargo.cid);
if (prod_amount < 10) continue;
local transp_amount = GSIndustry.GetLastMonthTransported(ind, cargo.cid);
if (prod_amount - transp_amount < 10) continue;
local loc = GSIndustry.GetLocation(ind);
sources[num_sources] <- {ind=ind, prod=prod_amount, transp=transp_amount, loc=loc};
num_sources += 1;
}
}
if (cargo.effect != GSCargo.TE_NONE) {
// For 'town effect' cargoes, check the towns for sufficient available production.
foreach (town, _ in GSTownList()) {
local prod_amount = GSTown.GetLastMonthProduction(town, cargo.cid);
if (prod_amount < 10) continue;
local transp_amount = GSTown.GetLastMonthSupplied(town, cargo.cid);
if (prod_amount - transp_amount < 10) continue;
local loc = GSTown.GetLocation(town);
sources[num_sources] <- {town=town, prod=prod_amount, transp=transp_amount, loc=loc};
num_sources += 1;
}
}
return sources;
}
// Find destinations for the cargo.
// @param cargo_id Cargo index (index in this.cargoes).
// @param company Company to inspect.
// @return A list of destinations, tables 'ind' or 'town' id, and a 'loc' location.
function FMainClass::FindDestinations(cargo_id, company)
{
local cargo = this.cargoes[cargo_id];
local num_dests = 0;
local dests = {};
if (cargo.freight) {
// Assume all industries are willing to accept the cargo.
foreach (ind, _ in GSIndustryList_CargoAccepting(cargo.cid)) {
local loc = GSIndustry.GetLocation(ind);
dests[num_dests] <- {ind=ind, loc=loc};
num_dests += 1;
}
}
if (cargo.effect != GSCargo.TE_NONE) {
// Find towns with sufficient rating.
local acceptable_ratings = [
GSTown.TOWN_RATING_MEDIOCRE, GSTown.TOWN_RATING_GOOD,
GSTown.TOWN_RATING_VERY_GOOD, GSTown.TOWN_RATING_EXCELLENT,
GSTown.TOWN_RATING_OUTSTANDING, GSTown.TOWN_RATING_INVALID
];
foreach (town, _ in GSTownList()) {
local rating = GSTown.GetRating(town, company);
if (rating in acceptable_ratings) {
local loc = GSTown.GetLocation(town);
dests[num_dests] <- {town=town, loc=loc};
num_dests += 1;
}
}
}
return dests;
}
// Construct a score for the distance
// @param desired Desired distance.
// @param actual Actual distance.
// @return Score for the distance.
function FMainClass::GetDistanceScore(desired, actual)
{
if (actual < desired) return 1000 - 3 * (desired - actual); // Too close gets punished hard.
return 1000 - (actual - desired);
}
// Try to find a challenge for a given cargo and a desired distance.
// @param cargo Cargo entry from FMainClass.cargoes (table with 'cid', 'freight', 'effect').
// @param distance Desired distance between source and target.
// @param cid Company to find a challenge for.
// @return Best accepting industry to use, or 'null' if no industry-pair found.
function FMainClass::FindChallenge(cargo_id, distance, cid)
{
local prods = this.FindSources(cargo_id);
local accepts = this.FindDestinations(cargo_id, cid);
local cdata = this.companies[cid];
local best_score = 0; // Best overall distance.
local best_accept = null; // Best accepting to target.
foreach (_, accept in accepts) {
if (cdata != null && cdata.HasGoal(cargo_id, accept)) continue; // Prevent duplicates.
local min_prod_distance = distance * 2; // Smallest found distance to the accepting industry.
local prod_score = GetDistanceScore(distance, min_prod_distance);
foreach (_, prod in prods) {
local actual_dist = GSTile.GetDistanceManhattanToTile(accept.loc, prod.loc);
if (actual_dist > distance * 2) continue; // Too far away, skip.
if (actual_dist < min_prod_distance) {
min_prod_distance = actual_dist;
prod_score = this.GetDistanceScore(distance, min_prod_distance);
if (min_prod_distance < distance && prod_score < best_score) break;
}
}
if (prod_score > best_score) { // The accepting industry is better than what we have.
prod_score = best_score;
best_accept = accept;
}
}
return best_accept;
}
// Try to add a goal for a company.
function FMainClass::CreateChallenge(cid)
{
local attempt = 0;
while (attempt < 20) {
local cargo = GSBase.RandRange(this.num_cargoes);
local distance = GSBase.RandRange(200) + 50; // Distance 50 .. 250 tiles.
local accept = FindChallenge(cargo, distance, cid);
if (accept != null) {
local cdata = this.companies[cid];
local amount = GSBase.RandRange(100) + 1;
if (amount < 10) {
amount = amount * 25; // 25 .. 225
} else if (amount < 10 + 35) {
amount = 10 * 25 + (amount - 10) * 50; // 250..1950
} else {
amount = 10 * 25 + 35 * 50 + (amount - 10 - 35) * 100; // 2000..7500
}
if (cdata != null) {
cdata.AddActiveGoal(cargo, accept, amount);
local destination, destination_name, destination_string, goal_type;
if ("town" in accept) {
destination = accept.town;
destination_name = GSTown.GetName(destination);
destination_string = GSText(GSText.STR_TOWN_NAME, destination);
goal_type = GSGoal.GT_TOWN;
} else {
destination = accept.ind;
destination_name = GSIndustry.GetName(destination);
destination_string = GSText(GSText.STR_INDUSTRY_NAME, destination);
goal_type = GSGoal.GT_INDUSTRY;
}
local goal_text = GSText(GSText.STR_COMPANY_GOAL, amount, 1 << cargo, destination_string);
GSLog.Info("Company " + cid + ": " + amount + " of " +
GSCargo.GetCargoLabel(this.cargoes[cargo].cid) +
" to " + destination_name);
GSGoal.New(cid, goal_text, goal_type, destination);
break;
}
}
attempt += 1;
}
}
function FMainClass::Start()
{
this.Sleep(1); // Wait for the game to start.
this.ExamineCargoes();
// local accept = FindChallenge(0, 50, GSCompany.COMPANY_FIRST); // Mail challenge
// if (accept != null) {
// if ("town" in accept) {
// GSLog.Info("Use town " + GSTown.GetName(accept.town));
// } else {
// GSLog.Info("Use industry " + GSIndustry.GetName(accept.ind));
// }
// }
// Construct empty companies.
this.companies = {};
for (local cid = GSCompany.COMPANY_FIRST; cid <= GSCompany.COMPANY_LAST; cid++) {
this.companies[cid] <- null;
}
// Main event loop.
local companies_timeout = 0;
local goal_timeout = 0;
while (true) {
// Check for new or disappeared companies.
if (companies_timeout <= 0) {
for (local cid = GSCompany.COMPANY_FIRST; cid <= GSCompany.COMPANY_LAST; cid++) {
if (GSCompany.ResolveCompanyID(cid) == GSCompany.COMPANY_INVALID) {
if (this.companies[cid] != null) {
// XXX Handle company disappearing
GSLog.Info("Deleted company " + cid);
}
this.companies[cid] = null;
} else {
if (this.companies[cid] == null) {
this.companies[cid] = CompanyData(cid);
GSLog.Info("Created company " + cid);
}
}
}
companies_timeout = 50 * 74; // 50 days until the next companies check.
}
// Check for having to create new goals.
if (goal_timeout <= 0) {
local total_missing = 0; // Total number of missing goals.
local best_cid = null;
local cid_missing = 0;
foreach (cid, cdata in companies) {
if (cdata == null) continue;
local missing = cdata.GetMissingGoalCount();
total_missing += missing;
// Find company with most missing goals.
if (missing > cid_missing) {
best_cid = cid;
cid_missing = missing;
}
}
if (best_cid != null) this.CreateChallenge(best_cid);
if (total_missing > 1) {
goal_timeout = 1 * 74; // If more missing goals, wait only a short while.
} else {
goal_timeout = 30 * 74;
}
}
// local lake_news = GSText(GSText.STR_LAKE_NEWS);
// GSNews.Create(GSNews.NT_GENERAL, lake_news, GSCompany.COMPANY_INVALID);
// GSGoal.Question(1, GSCompany.COMPANY_INVALID, lake_news, GSGoal.QT_INFORMATION, GSGoal.BUTTON_GO);
// Sleep until the next event.
local delay_time = 5000;
if (delay_time > companies_timeout) delay_time = companies_timeout;
if (delay_time > goal_timeout) delay_time = goal_timeout;
// XXX Perhaps check for company events?
if (delay_time > 0) this.Sleep(delay_time);
companies_timeout -= delay_time;
goal_timeout -= delay_time;
}
}