diff --git a/src/model/GameConfig.ts b/src/model/GameConfig.ts
index 9c04cf8..bb1f5db 100644
--- a/src/model/GameConfig.ts
+++ b/src/model/GameConfig.ts
@@ -2,6 +2,7 @@
///
///
///
+///
///
///
///
@@ -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));
diff --git a/src/model/Utils.ts b/src/model/Utils.ts
index 3ac5d80..c7f096e 100644
--- a/src/model/Utils.ts
+++ b/src/model/Utils.ts
@@ -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}`;
}
diff --git a/src/model/resource/CompoundManager.ts b/src/model/resource/CompoundManager.ts
new file mode 100644
index 0000000..7941f5c
--- /dev/null
+++ b/src/model/resource/CompoundManager.ts
@@ -0,0 +1,25 @@
+///
+
+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;
+ }
+}
diff --git a/src/model/resource/Follower.ts b/src/model/resource/Follower.ts
index dc3542c..934df5e 100644
--- a/src/model/resource/Follower.ts
+++ b/src/model/resource/Follower.ts
@@ -1,4 +1,5 @@
///
+///
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 = 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;
}
}
diff --git a/src/model/resource/Job.ts b/src/model/resource/Job.ts
index ad812c3..c55f0e2 100644
--- a/src/model/resource/Job.ts
+++ b/src/model/resource/Job.ts
@@ -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) &&
diff --git a/src/model/resource/Money.ts b/src/model/resource/Money.ts
index abe21c8..9394941 100644
--- a/src/model/resource/Money.ts
+++ b/src/model/resource/Money.ts
@@ -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;
};
diff --git a/src/model/resource/SharedTypes.ts b/src/model/resource/SharedTypes.ts
index 63a2c87..92551c1 100644
--- a/src/model/resource/SharedTypes.ts
+++ b/src/model/resource/SharedTypes.ts
@@ -20,6 +20,7 @@ enum ResourceKey {
other = 'other',
atheism = 'atheism',
pastors = 'pastors',
+ compoundManagers = 'compoundManagers',
money = 'money',
cryptoCurrency = 'cryptoCurrency',
cryptoMarket = 'cryptoMarket',
diff --git a/src/render/DebugRenderer.ts b/src/render/DebugRenderer.ts
index 6c68479..1be2d6e 100644
--- a/src/render/DebugRenderer.ts
+++ b/src/render/DebugRenderer.ts
@@ -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');