diff --git a/public/css/debugger.css b/public/css/debugger.css
index 8fd04d6..cf30440 100644
--- a/public/css/debugger.css
+++ b/public/css/debugger.css
@@ -42,6 +42,9 @@ body, html {
#resource-container-religion .resource {
background-color: #ccf;
}
+#resource-container-job .resource {
+ background-color: #fcf;
+}
#resource-container-consumable .resource {
background-color: #cfc;
}
diff --git a/src/main.ts b/src/main.ts
index bc2a1f2..253178e 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -29,9 +29,11 @@ function startGame (state: GameState, renderer: IRenderer): void {
const config: GameConfig = new GameConfig();
// debug values to make the game play faster while testing
- config.baseTitheAmount = 1000;
- config.baseCryptoReturnAmount = 100;
- config.baseCredibilityRestoreRate = 5;
+ config.cfgTitheAmount = 1000;
+ config.cfgTimeBetweenTithes = 5000;
+ config.cfgCryptoReturnAmount = 100;
+ config.cfgCredibilityRestoreRate = 5;
+ config.cfgPastorRecruitRate = 0.5;
const renderer: IRenderer = new DebugRenderer();
const state: GameState = config.generateState();
diff --git a/src/model/GameConfig.ts b/src/model/GameConfig.ts
index ff655d5..0dabdbd 100644
--- a/src/model/GameConfig.ts
+++ b/src/model/GameConfig.ts
@@ -1,12 +1,14 @@
///
+///
+///
///
///
+///
///
+///
///
///
///
-///
-///
class GameConfig {
public worldPopulation: number = 790000000;
@@ -21,12 +23,15 @@ class GameConfig {
public relOtherShare: number = 0.02;
public relNoneShare: number = 0.16;
- public baseTitheAmount: number = 10;
- public baseCryptoReturnAmount: number = 1;
- public baseCredibilityRestoreRate: number = 0.25;
+ public cfgTitheAmount: number = 10;
+ public cfgTimeBetweenTithes: number = 30000;
+ public cfgCryptoReturnAmount: number = 1;
+ public cfgCredibilityRestoreRate: number = 0.25;
+ public cfgPastorRecruitRate: number = 0.01;
public generateState (): GameState {
const state: GameState = new GameState();
+ state.config = this;
// create player organization
state.addResource('plorg', new PlayerOrg());
@@ -64,17 +69,19 @@ class GameConfig {
'Non-Religious', 'Atheists and agnostics.',
this.relNoneShare * this.worldPopulation));
+ // add jobs
+ state.addResource('pstor', new Pastor());
+
// add resources
- state.addResource('money', new Money(3.50,
- this.baseTitheAmount, this.baseCryptoReturnAmount));
+ state.addResource('money', new Money(3.50));
state.addResource('crpto', new CryptoCurrency());
state.addResource('tents', new Tent());
state.addResource('house', new House());
state.addResource('cmpnd', new Compound());
+ state.addResource('chrch', new Church());
// add passive resources
- state.addResource('creds', new Credibility(
- this.baseCredibilityRestoreRate));
+ state.addResource('creds', new Credibility());
return state;
}
diff --git a/src/model/GameState.ts b/src/model/GameState.ts
index 3d62599..08263f0 100644
--- a/src/model/GameState.ts
+++ b/src/model/GameState.ts
@@ -2,6 +2,8 @@ class GameState {
private _versionMaj: number = 0;
private _versionMin: number = 1;
+ public config: GameConfig;
+
private _timeSinceSave: number = 0;
private readonly _timeBetweenSaves: number = 10000;
diff --git a/src/model/resource/Church.ts b/src/model/resource/Church.ts
new file mode 100644
index 0000000..f65964b
--- /dev/null
+++ b/src/model/resource/Church.ts
@@ -0,0 +1,11 @@
+///
+
+class Church extends Infrastructure {
+ constructor () {
+ super('Churches',
+ 'Preaching grounds for 2 pastors.');
+ this.cost.money = 10000;
+ this._costMultiplier.money = 1.01;
+ this._baseMax = 2;
+ }
+}
diff --git a/src/model/resource/Compound.ts b/src/model/resource/Compound.ts
index 05af056..0195089 100644
--- a/src/model/resource/Compound.ts
+++ b/src/model/resource/Compound.ts
@@ -11,7 +11,7 @@ class Compound extends Infrastructure {
public isUnlocked (state: GameState): boolean {
if (this._isUnlocked) return true;
const tents: IResource = state.getResource('tents');
- if (tents.value === tents.max(state)) {
+ if (tents.value >= 5) {
this._isUnlocked = true;
}
return this._isUnlocked;
diff --git a/src/model/resource/Credibility.ts b/src/model/resource/Credibility.ts
index 0ac71a4..a01e024 100644
--- a/src/model/resource/Credibility.ts
+++ b/src/model/resource/Credibility.ts
@@ -3,8 +3,7 @@
class Credibility extends Passive {
private _lastValue: number = 100;
- constructor (
- private _baseRestoreRate: number) {
+ constructor () {
super(
'Credibility',
'Affects your ability to recruit and retain followers.',
@@ -16,6 +15,6 @@ class Credibility extends Passive {
}
public inc (state: GameState): number {
- return this._baseRestoreRate;
+ return state.config.cfgCredibilityRestoreRate;
}
}
diff --git a/src/model/resource/CryptoCurrency.ts b/src/model/resource/CryptoCurrency.ts
index 9586e66..3455663 100644
--- a/src/model/resource/CryptoCurrency.ts
+++ b/src/model/resource/CryptoCurrency.ts
@@ -1,6 +1,8 @@
///
class CryptoCurrency extends Purchasable {
+ public readonly valueInWholeNumbers: boolean = false;
+
constructor () {
super('Faithcoin',
"A crypto coin that can't be spent directly, but provides a steady stream of passive income.");
diff --git a/src/model/resource/House.ts b/src/model/resource/House.ts
index d04c919..2ab42c9 100644
--- a/src/model/resource/House.ts
+++ b/src/model/resource/House.ts
@@ -17,8 +17,8 @@ class House extends Infrastructure {
public isUnlocked (state: GameState): boolean {
if (this._isUnlocked) return true;
- const tents: IResource = state.getResource('tents');
- if (tents.value === tents.max(state)) {
+ const compounds: IResource = state.getResource('cmpnd');
+ if (compounds.value > 0) {
this._isUnlocked = true;
}
return this._isUnlocked;
diff --git a/src/model/resource/IResource.ts b/src/model/resource/IResource.ts
index 8c58c44..537aebe 100644
--- a/src/model/resource/IResource.ts
+++ b/src/model/resource/IResource.ts
@@ -1,5 +1,6 @@
enum ResourceType {
Religion = 'religion',
+ Job = 'job',
Consumable = 'consumable',
Infrastructure = 'infrastructure',
Passive = 'passive'
@@ -11,6 +12,7 @@ interface IResource {
resourceType: ResourceType;
value: number;
+ valueInWholeNumbers: boolean;
clickText: string;
clickDescription: string;
diff --git a/src/model/resource/Job.ts b/src/model/resource/Job.ts
new file mode 100644
index 0000000..081ffff
--- /dev/null
+++ b/src/model/resource/Job.ts
@@ -0,0 +1,69 @@
+///
+
+abstract class Job implements IResource {
+ public readonly resourceType: ResourceType = ResourceType.Job;
+ public value: number = 0;
+ public readonly valueInWholeNumbers: boolean = true;
+
+ public clickText: string = 'Hire';
+ public clickDescription: string = 'Promote one of your followers.';
+
+ public cost: { [key: string]: number } = { };
+
+ protected _costMultiplier: { [key: string]: number } = { };
+ protected _isUnlocked: boolean = false;
+
+ constructor (
+ public readonly name: string,
+ public readonly description: string
+ ) { }
+
+ public clickAction (state: GameState): void {
+ if (this._availableJobs(state) <= 0) {
+ state.log('You have no unemployed followers to promote.');
+ return;
+ }
+ if (this.value < this.max(state) && state.deductCost(this.cost)) {
+ this.value++;
+ state.log(this._hireLog(1, state));
+ for (const rkey of Object.keys(this._costMultiplier)) {
+ this.cost[rkey] *= this._costMultiplier[rkey];
+ }
+ }
+ }
+
+ public inc (state: GameState): number | null {
+ return null;
+ }
+
+ public max (state: GameState): number | null {
+ return null;
+ }
+
+ public advanceAction (time: number, state: GameState): void {
+ return;
+ }
+
+ public isUnlocked (state: GameState): boolean {
+ return this._isUnlocked;
+ }
+
+ protected _availableJobs (state: GameState): number {
+ // number of followers minus the number of filled jobs
+ const followers: number = state.getResource('plorg').value;
+ const hired: number = state.getResources()
+ .reduce((tot: number, rkey: string): number => {
+ const res: IResource = state.getResource(rkey);
+ return res.resourceType === ResourceType.Job
+ ? tot + res.value
+ : tot;
+ }, 0);
+ let max: number = followers - hired;
+ if (max < 0) max = 0;
+ return max;
+ }
+
+ protected _hireLog (amount: number, state: GameState): string {
+ return `You hired ${amount} x ${this.name}.`;
+ }
+}
diff --git a/src/model/resource/Money.ts b/src/model/resource/Money.ts
index 2cf2f94..1ae8a54 100644
--- a/src/model/resource/Money.ts
+++ b/src/model/resource/Money.ts
@@ -5,11 +5,10 @@ class Money extends Purchasable {
public resourceType: ResourceType = ResourceType.Consumable;
public cost: { [key: string]: number } = { };
+ public readonly valueInWholeNumbers: boolean = false;
constructor (
- public value: number,
- private readonly _baseTitheAmount: number,
- private readonly _baseCryptoReturnAmount: number
+ public value: number
) {
super('Money', 'Used to purchase goods and services.');
this.clickText = 'Collect Tithes';
@@ -22,8 +21,13 @@ class Money extends Purchasable {
}
public inc (state: GameState): number {
+ let inc: number = 0;
+
// crypto currency
- return state.getResource('crpto').value * this._baseCryptoReturnAmount;
+ inc += state.getResource('crpto').value
+ * state.config.cfgCryptoReturnAmount;
+
+ return inc;
}
protected _purchaseAmount (state: GameState): number {
@@ -33,12 +37,12 @@ class Money extends Purchasable {
return 0;
}
const diff: number = state.now - this._lastCollectionTime;
- if (diff < 30000) {
- const lost: number = 30000 / diff / 3;
+ if (diff < state.config.cfgTimeBetweenTithes) {
+ const lost: number = state.config.cfgTimeBetweenTithes / diff / 3;
state.getResource('creds').value -= lost;
}
// each follower gives you $10
- const tithings: number = plorg.value * this._baseTitheAmount;
+ const tithings: number = plorg.value * state.config.cfgTitheAmount;
this._lastCollectionTime = state.now;
return tithings;
}
diff --git a/src/model/resource/Passive.ts b/src/model/resource/Passive.ts
index fe5a4e9..63a91c3 100644
--- a/src/model/resource/Passive.ts
+++ b/src/model/resource/Passive.ts
@@ -6,6 +6,7 @@ abstract class Passive implements IResource {
public readonly clickDescription: null = null;
public readonly cost: null = null;
public readonly clickAction: null = null;
+ public readonly valueInWholeNumbers: boolean = false;
protected _baseMax: number | null;
protected _baseInc: number | null;
diff --git a/src/model/resource/Pastor.ts b/src/model/resource/Pastor.ts
new file mode 100644
index 0000000..1b662cd
--- /dev/null
+++ b/src/model/resource/Pastor.ts
@@ -0,0 +1,37 @@
+///
+
+class Pastor extends Job {
+ private _timeSinceLastTithe: number = 0;
+
+ constructor () {
+ super('Pastors', 'Leaders of the faith.');
+ }
+
+ public max (state: GameState): number {
+ return state.getResource('chrch').value * 2;
+ }
+
+ public isUnlocked (state: GameState): boolean {
+ if (this._isUnlocked) return true;
+ this._isUnlocked = state.getResource('chrch').isUnlocked(state);
+ }
+
+ public advanceAction (time: number, state: GameState): void {
+ this._timeSinceLastTithe += time;
+ if (this._timeSinceLastTithe >= state.config.cfgTimeBetweenTithes) {
+ const money: IResource = state.getResource('money');
+ const plorg: IResource = state.getResource('plorg');
+ // each pastor can collect from up to 100 followers
+ let tithed: number = this.value * 100;
+ if (plorg.value < tithed) tithed = plorg.value;
+ let collected: number = tithed * state.config.cfgTitheAmount;
+ if (collected > money.max(state) - money.value)
+ collected = money.max(state) - money.value;
+ if (collected > 0) {
+ money.value += collected;
+ state.log(`Your pastors collected $${state.formatNumber(collected)} in tithings from ${state.formatNumber(tithed)} followers.`);
+ }
+ this._timeSinceLastTithe = 0;
+ }
+ }
+}
diff --git a/src/model/resource/PlayerOrg.ts b/src/model/resource/PlayerOrg.ts
index fd309da..9131471 100644
--- a/src/model/resource/PlayerOrg.ts
+++ b/src/model/resource/PlayerOrg.ts
@@ -6,9 +6,9 @@ class PlayerOrg implements IResource {
public readonly description: string = 'In you they trust.';
public cost: { [key: string]: number } = { };
- public readonly inc: null = null;
public value: number = 0;
+ public readonly valueInWholeNumbers: boolean = true;
public clickText: string = 'Recruit';
public clickDescription: string = 'Gather new followers.';
@@ -53,6 +53,16 @@ class PlayerOrg implements IResource {
}
}
+ public inc (state: GameState): number {
+ let inc: number = 0;
+
+ // pastor auto-recruit
+ inc += state.getResource('pstor').value
+ * state.config.cfgPastorRecruitRate;
+
+ return inc;
+ }
+
public advanceAction (time: number, state: GameState): void {
// chance to lose some followers every 10s if credibility < 100%
if (state.now - this._lastLostTime > 10000) {
diff --git a/src/model/resource/Purchasable.ts b/src/model/resource/Purchasable.ts
index fa40c5c..0620059 100644
--- a/src/model/resource/Purchasable.ts
+++ b/src/model/resource/Purchasable.ts
@@ -3,6 +3,7 @@
abstract class Purchasable implements IResource {
public readonly resourceType: ResourceType = ResourceType.Consumable;
public value: number = 0;
+ public valueInWholeNumbers: boolean = true;
public clickText: string = 'Purchase';
public clickDescription: string = 'Purchase';
diff --git a/src/model/resource/Religion.ts b/src/model/resource/Religion.ts
index b645147..4fa83cd 100644
--- a/src/model/resource/Religion.ts
+++ b/src/model/resource/Religion.ts
@@ -9,6 +9,7 @@ class Religion implements IResource {
public readonly cost: null = null;
public readonly max: null = null;
public readonly inc: null = null;
+ public readonly valueInWholeNumbers: boolean = true;
constructor (
public readonly name: string,
diff --git a/src/render/DebugRenderer.ts b/src/render/DebugRenderer.ts
index f10458d..1649e41 100644
--- a/src/render/DebugRenderer.ts
+++ b/src/render/DebugRenderer.ts
@@ -82,7 +82,10 @@ class DebugRenderer implements IRenderer {
el.getElementsByClassName('resource-value')[0];
const elT: Element =
el.getElementsByClassName('resource-max')[0];
- elV.innerHTML = state.formatNumber(resource.value);
+ const value: number = resource.valueInWholeNumbers
+ ? Math.floor(resource.value)
+ : resource.value;
+ elV.innerHTML = state.formatNumber(value);
elT.innerHTML = resource.max !== null
&& resource.max(state) !== null
? ` / ${state.formatNumber(resource.max(state))}`