added compound manager, followers quit their jobs when you run out of money

This commit is contained in:
Rudis Muiznieks 2021-09-11 20:37:33 -05:00
parent 5fd789ca88
commit 2126ed483b
8 changed files with 154 additions and 42 deletions

View File

@ -2,6 +2,7 @@
/// <reference path="./resource/BuildingPermit.ts" />
/// <reference path="./resource/Church.ts" />
/// <reference path="./resource/Compound.ts" />
/// <reference path="./resource/CompoundManager.ts" />
/// <reference path="./resource/Credibility.ts" />
/// <reference path="./resource/CryptoCurrency.ts" />
/// <reference path="./resource/CryptoMarket.ts" />
@ -58,12 +59,19 @@ class GameConfig {
};
public cfgSalary: ResourceNumber = {
pastors: 7.5,
pastors: 250,
compoundManagers: 1000,
};
public cfgCapacity: { [key in ResourceKey]?: ResourceNumber } = {
churches: { pastors: 2 },
compounds: { churches: 1, houses: 2, money: 500000, tents: 10 },
compounds: {
churches: 1,
compoundManagers: 1,
houses: 2,
money: 500000,
tents: 10,
},
houses: { followers: 10 },
megaChurches: { pastors: 5 },
tents: { followers: 2 },
@ -78,6 +86,8 @@ class GameConfig {
public cfgCryptoMarketGrowthBias = 0.1;
public cfgDefaultSellMultiplier = 0.5;
public cfgFollowerGainLossLogTimer = 10000;
public cfgNoMoneyQuitRate = 0.2;
public cfgNoMoneyQuitTime = 10000;
public cfgPassiveMax = 100;
public cfgPastorRecruitRate = 0.01;
public cfgPastorTitheCollectionFollowerMax = 100;
@ -184,6 +194,7 @@ class GameConfig {
// add jobs
state.addResource(ResourceKey.pastors, new Pastor());
state.addResource(ResourceKey.compoundManagers, new CompoundManager());
// add resources
state.addResource(ResourceKey.money, new Money(3.5));

View File

@ -14,10 +14,15 @@ function formatNumber(num: number): string {
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
let item: UnitLookup | undefined;
for (item of lookup.slice().reverse()) {
if (num >= item.value) break;
if (Math.abs(num) >= item.value) break;
}
return item !== undefined
? (num / item.value).toFixed(numberFormatDigits).replace(rx, '$1') +
item.symbol
: num.toFixed(numberFormatDigits).replace(rx, '$1');
const sign = num < 0 ? '-' : '';
const number =
item !== undefined
? (Math.abs(num) / item.value)
.toFixed(numberFormatDigits)
.replace(rx, '$1') + item.symbol
: Math.abs(num).toFixed(numberFormatDigits).replace(rx, '$1');
return `${sign}${number}`;
}

View File

@ -0,0 +1,25 @@
/// <reference path="./Job.ts" />
class CompoundManager extends Job {
constructor() {
super(
'Compound Managers',
'compound manager',
'compound managers',
'Automatically purchase tents, houses, and churches when money and compound space permits.'
);
}
public max: (state: GameState) => number = (state) => {
return (
(state.resource.compounds?.value ?? 0) *
(state.config.cfgCapacity.compounds?.compoundManagers ?? 0)
);
};
public isUnlocked(state: GameState): boolean {
if (this._isUnlocked) return true;
this._isUnlocked = state.resource.compounds?.isUnlocked(state) === true;
return this._isUnlocked;
}
}

View File

@ -1,4 +1,5 @@
/// <reference path="./IResource.ts" />
/// <reference path="./Job.ts" />
class Follower implements IResource {
public readonly resourceType = ResourceType.religion;
@ -24,6 +25,8 @@ class Follower implements IResource {
private _lastRecruitmentLog = 0;
private _followerSources: ResourceNumber = {};
private _followerDests: ResourceNumber = {};
private _timeSinceLastQuit = 0;
private _quitTracker: ResourceNumber = {};
public max(state: GameState): number {
let max = state.config.cfgInitialMax.followers ?? 0;
@ -84,7 +87,7 @@ class Follower implements IResource {
}
public advanceAction(time: number, state: GameState): void {
// chance to lose some followers every 10s if credibility < 100%
// chance to lose some followers if credibility < 100%
this._timeSinceLastLost += time;
if (this._timeSinceLastLost > state.config.cfgCredibilityFollowerLossTime) {
if (this.value > 0) {
@ -104,12 +107,40 @@ class Follower implements IResource {
this._timeSinceLastLost = 0;
}
// log lost and gained followers every 10s
// chance for some followers to quit their jobs if money === 0
const money = state.resource.money;
const totalJobs = Job.totalJobs(state);
if (money !== undefined && money.value <= 0 && totalJobs > 0) {
this._timeSinceLastQuit += time;
if (this._timeSinceLastQuit > state.config.cfgNoMoneyQuitTime) {
let lost = Math.ceil(totalJobs * state.config.cfgNoMoneyQuitRate);
for (const rkey of Job.jobResources(state)) {
const job = state.resource[rkey];
if (job !== undefined && job.value > 0) {
if (job.value >= lost) {
job.addValue(lost * -1, state);
this._quitTracker[rkey] = lost;
break;
} else {
job.addValue(job.value * -1, state);
this._quitTracker[rkey] = job.value;
lost -= job.value;
}
}
}
this._timeSinceLastQuit = 0;
}
} else {
this._timeSinceLastQuit = 0;
}
// log lost, gained, and quit followers at regular intervals
if (
state.now - this._lastRecruitmentLog >
state.config.cfgFollowerGainLossLogTimer &&
(Object.keys(this._followerSources).length > 0 ||
Object.keys(this._followerDests).length > 0)
Object.keys(this._followerDests).length > 0 ||
Object.keys(this._quitTracker).length > 0)
) {
if (Object.keys(this._followerDests).length > 0) {
let msg = '';
@ -155,6 +186,28 @@ class Follower implements IResource {
}: ${msg}`
);
}
if (Object.keys(this._quitTracker).length > 0) {
let msg = '';
let total = 0;
for (const key in this._quitTracker) {
const rkey = <ResourceKey>key;
const job = state.resource[rkey];
const followers = this._quitTracker[rkey];
if (job !== undefined && followers !== undefined) {
if (msg !== '') msg += ', ';
msg += `${formatNumber(followers)} ${
followers > 1 ? job.pluralName : job.singularName
}`;
total += followers;
delete this._quitTracker[rkey];
}
}
state.log(
`${formatNumber(total)} ${
total > 1 ? this.pluralName : this.singularName
} quit their jobs: ${msg}`
);
}
this._lastRecruitmentLog = state.now;
}
}

View File

@ -15,7 +15,8 @@ abstract class Job implements IResource {
description: 'Promote one of your followers.',
isEnabled: (state: GameState): boolean =>
(this.max === undefined || this.value < this.max(state)) &&
this._availableJobs(state) > 0,
Job.availableJobs(state) > 0 &&
(state.resource.money?.value ?? 0) > 0,
performAction: (state: GameState): void => {
this._promoteFollower(state);
},
@ -40,6 +41,39 @@ abstract class Job implements IResource {
public readonly description: string
) {}
public static jobResources(state: GameState): ResourceKey[] {
return state.resources.filter((rkey) => {
const res = state.resource[rkey];
return res !== undefined && res.resourceType === ResourceType.job;
});
}
public static availableJobs(state: GameState): number {
// number of followers minus the number of filled jobs
const followers = state.resource.followers?.value ?? 0;
const hired = state.resources.reduce(
(tot: number, rkey: ResourceKey): number => {
const res = state.resource[rkey];
return res?.resourceType === ResourceType.job ? tot + res.value : tot;
},
0
);
return followers - hired;
}
public static totalJobs(state: GameState): number {
// number of followers minus the number of filled jobs
const followers = state.resource.followers?.value ?? 0;
const hired = state.resources.reduce(
(tot: number, rkey: ResourceKey): number => {
const res = state.resource[rkey];
return res?.resourceType === ResourceType.job ? tot + res.value : tot;
},
0
);
return followers - hired;
}
public addValue(amount: number): void {
this.value += amount;
if (this.value < 0) this.value = 0;
@ -51,39 +85,14 @@ abstract class Job implements IResource {
public advanceAction(_time: number, state: GameState): void {
// if we're out of followers then the jobs also vacate
const avail = this._availableJobs(state);
const avail = Job.availableJobs(state);
if (avail < 0 && this.value > 0) {
this.addValue(avail);
}
return;
}
protected _availableJobs(state: GameState): number {
// number of followers minus the number of filled jobs
const followers = state.resource.followers?.value ?? 0;
const hired = state.resources.reduce(
(tot: number, rkey: ResourceKey): number => {
const res = state.resource[rkey];
return res?.resourceType === ResourceType.job ? tot + res.value : tot;
},
0
);
return followers - hired;
}
protected _totalPayroll(state: GameState): number {
// number of followers minus the number of filled jobs
const followers = state.resource.followers?.value ?? 0;
const hired = state.resources.reduce(
(tot: number, rkey: ResourceKey): number => {
const res = state.resource[rkey];
return res?.resourceType === ResourceType.job ? tot + res.value : tot;
},
0
);
return followers - hired;
}
protected _hireLog(amount: number, _state: GameState): string {
return amount > 0
? `You hired ${amount} ${
@ -95,7 +104,7 @@ abstract class Job implements IResource {
}
private _promoteFollower(state: GameState): void {
if (this._availableJobs(state) <= 0) return;
if (Job.availableJobs(state) <= 0) return;
if (
this.max !== undefined &&
this.value < this.max(state) &&

View File

@ -47,6 +47,10 @@ class Money implements IResource {
(state.resource.pastors?.value ?? 0) *
(state.config.cfgSalary.pastors ?? 0);
inc -=
(state.resource.compoundManagers?.value ?? 0) *
(state.config.cfgSalary.compoundManagers ?? 0);
return inc;
};

View File

@ -20,6 +20,7 @@ enum ResourceKey {
other = 'other',
atheism = 'atheism',
pastors = 'pastors',
compoundManagers = 'compoundManagers',
money = 'money',
cryptoCurrency = 'cryptoCurrency',
cryptoMarket = 'cryptoMarket',

View File

@ -131,9 +131,13 @@ class DebugRenderer implements IRenderer {
}
}
}
if (resource.inc !== undefined && resource.inc(state) > 0) {
const elI = el.getElementsByClassName('resource-inc')[0];
elI.innerHTML = ` +${formatNumber(resource.inc(state))}/s`;
const inc =
resource.inc !== undefined ? resource.inc(state) : undefined;
const elI = el.getElementsByClassName('resource-inc')[0];
if (inc !== undefined && inc !== 0) {
elI.innerHTML = ` ${inc > 0 ? '+' : ''}${formatNumber(inc)}/s`;
} else if (elI.innerHTML !== '') {
elI.innerHTML = '';
}
if (this._handleClick) {
const elC = el.getElementsByClassName('resource-cost');