diff --git a/.eslintrc.json b/.eslintrc.json
index a9ce638..e4a913e 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -37,8 +37,6 @@
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-implicit-any-catch": "error",
"@typescript-eslint/no-invalid-void-type": "error",
- "@typescript-eslint/no-parameter-properties": ["error", {
- "allows": ["public", "readonly", "public readonly"]}],
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "warn",
diff --git a/src/model/GameConfig.ts b/src/model/GameConfig.ts
index 84211b0..166520d 100644
--- a/src/model/GameConfig.ts
+++ b/src/model/GameConfig.ts
@@ -4,27 +4,28 @@
///
///
///
+///
///
-///
+///
///
///
-///
///
///
class GameConfig {
public worldPopulation = 790000000;
- public numberFormatDigits = 1;
// religion configs
- public relChristianitySharer = 0.325;
- public relIslamShare = 0.215;
- public relHinduismShare = 0.16;
- public relBuddhismShare = 0.06;
- public relSikhismShare = 0.04;
- public relJudaismShare = 0.02;
- public relOtherShare = 0.02;
- public relNoneShare = 0.16;
+ public cfgReligion: ResourceNumber = {
+ christianity: 0.325,
+ islam: 0.215,
+ hinduism: 0.16,
+ buddhism: 0.06,
+ sikhism: 0.04,
+ judaism: 0.02,
+ other: 0.02,
+ atheism: 0.16,
+ };
// general configs
public cfgInitialMax: ResourceNumber = {
@@ -81,40 +82,40 @@ class GameConfig {
const state = new GameState(this);
// create player organization
- state.addResource(ResourceKey.playerOrg, new PlayerOrg());
+ state.addResource(ResourceKey.playerOrg, new Follower());
// create world religions
state.addResource(ResourceKey.christianity, new Religion(
- 'Christianity', 'God, Jesus, Bible, churches.',
- this.relChristianitySharer * this.worldPopulation));
+ 'christian', 'christians', 'God, Jesus, Bible, churches.',
+ (this.cfgReligion.christianity ?? 0) * this.worldPopulation));
state.addResource(ResourceKey.islam, new Religion(
- 'Islam', 'God, Muhammad, Quran, mosques.',
- this.relIslamShare * this.worldPopulation));
+ 'muslim', 'muslims', 'God, Muhammad, Quran, mosques.',
+ (this.cfgReligion.islam ?? 0) * this.worldPopulation));
state.addResource(ResourceKey.hinduism, new Religion(
- 'Hinduism', 'Dogma-free spiritualism.',
- this.relHinduismShare * this.worldPopulation));
+ 'hindu', 'hindus', 'Dogma-free spiritualism.',
+ (this.cfgReligion.hinduism ?? 0) * this.worldPopulation));
state.addResource(ResourceKey.buddhism, new Religion(
- 'Buddhism', 'The minimization of suffering.',
- this.relBuddhismShare * this.worldPopulation));
+ 'buddhist', 'buddhists', 'The minimization of suffering.',
+ (this.cfgReligion.buddhism ?? 0) * this.worldPopulation));
state.addResource(ResourceKey.sikhism, new Religion(
- 'Sikhism', 'Meditation and ten Gurus',
- this.relSikhismShare * this.worldPopulation));
+ 'sikh', 'sikhs', 'Meditation and ten Gurus',
+ (this.cfgReligion.sikhism ?? 0) * this.worldPopulation));
state.addResource(ResourceKey.judaism, new Religion(
- 'Judaism', 'God, Abraham, Torah, synagogues.',
- this.relJudaismShare * this.worldPopulation));
+ 'jew', 'jews', 'God, Abraham, Torah, synagogues.',
+ (this.cfgReligion.judaism ?? 0) * this.worldPopulation));
state.addResource(ResourceKey.other, new Religion(
- 'Other', 'A variety of belief systems.',
- this.relOtherShare * this.worldPopulation));
+ 'other', 'others', 'A variety of belief systems.',
+ (this.cfgReligion.other ?? 0) * this.worldPopulation));
state.addResource(ResourceKey.atheism, new Religion(
- 'Non-Religious', 'Atheists and agnostics.',
- this.relNoneShare * this.worldPopulation));
+ 'atheist', 'atheists', 'Atheists and agnostics.',
+ (this.cfgReligion.atheism ?? 0) * this.worldPopulation));
// add jobs
state.addResource(ResourceKey.pastors, new Pastor());
@@ -126,7 +127,7 @@ class GameConfig {
state.addResource(ResourceKey.houses, new House(this));
state.addResource(ResourceKey.churches, new Church(this));
state.addResource(ResourceKey.compounds, new Compound(this));
- state.addResource(ResourceKey.megaChurches, new MegaChurch(this));
+ state.addResource(ResourceKey.megaChurches, new Megachurch(this));
// add research
state.addResource(ResourceKey.buildingPermit, new BuildingPermit(this));
@@ -136,26 +137,4 @@ class GameConfig {
return state;
}
-
- public formatNumber (num: number): string {
- type UnitLookup = { value: number, symbol: string };
- const lookup: UnitLookup[] = [
- { value: 1, symbol: '' },
- { value: 1e3, symbol: 'K' },
- { value: 1e6, symbol: 'M' },
- { value: 1e9, symbol: 'G' },
- { value: 1e12, symbol: 'T' },
- { value: 1e15, symbol: 'P' },
- { value: 1e18, symbol: 'E' },
- ];
- const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
- let item: UnitLookup | undefined;
- for (item of lookup.slice().reverse()) {
- if (num >= item.value) break;
- }
- return item !== undefined
- ? (num / item.value).toFixed(
- this.numberFormatDigits).replace(rx, '$1') + item.symbol
- : num.toFixed(this.numberFormatDigits).replace(rx, '$1');
- }
}
diff --git a/src/model/GameState.ts b/src/model/GameState.ts
index fe90da2..933c1c5 100644
--- a/src/model/GameState.ts
+++ b/src/model/GameState.ts
@@ -1,3 +1,5 @@
+///
+
class GameState {
public readonly config: GameConfig;
@@ -69,15 +71,17 @@ class GameState {
}
}
- public performClick (resourceKey: ResourceKey): void {
+ public performAction (resourceKey: ResourceKey, actionIndex: number): void {
const resource = this._resources[resourceKey];
- if (resource === undefined || !resource.isUnlocked(this)) return;
+ if (resource === undefined || resource.userActions === undefined
+ || actionIndex > resource.userActions.length
+ || !resource.isUnlocked(this)) return;
- if (resource.clickAction !== undefined) {
- resource.clickAction(this);
- for (const callback of this.onResourceClick) {
- callback();
- }
+ const action = resource.userActions[actionIndex];
+
+ action.performAction(this);
+ for (const callback of this.onResourceClick) {
+ callback();
}
}
diff --git a/src/model/Utils.ts b/src/model/Utils.ts
new file mode 100644
index 0000000..a50dbec
--- /dev/null
+++ b/src/model/Utils.ts
@@ -0,0 +1,23 @@
+const numberFormatDigits = 1;
+
+function formatNumber (num: number): string {
+ type UnitLookup = { value: number, symbol: string };
+ const lookup: UnitLookup[] = [
+ { value: 1, symbol: '' },
+ { value: 1e3, symbol: 'K' },
+ { value: 1e6, symbol: 'M' },
+ { value: 1e9, symbol: 'G' },
+ { value: 1e12, symbol: 'T' },
+ { value: 1e15, symbol: 'P' },
+ { value: 1e18, symbol: 'E' },
+ ];
+ const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
+ let item: UnitLookup | undefined;
+ for (item of lookup.slice().reverse()) {
+ if (num >= item.value) break;
+ }
+ return item !== undefined
+ ? (num / item.value).toFixed(
+ numberFormatDigits).replace(rx, '$1') + item.symbol
+ : num.toFixed(numberFormatDigits).replace(rx, '$1');
+}
diff --git a/src/model/resource/BuildingPermit.ts b/src/model/resource/BuildingPermit.ts
index 97f76f5..37fa910 100644
--- a/src/model/resource/BuildingPermit.ts
+++ b/src/model/resource/BuildingPermit.ts
@@ -2,7 +2,9 @@
class BuildingPermit extends Research {
constructor (config: GameConfig) {
- super('Building Permit',
+ super(
+ 'building permit',
+ 'building permits',
'Unlocks several new buildings you can build outside of your compounds.');
this.cost.money = config.cfgInitialMax.buildingPermit;
}
diff --git a/src/model/resource/Church.ts b/src/model/resource/Church.ts
index 6e4e36b..40fbb2b 100644
--- a/src/model/resource/Church.ts
+++ b/src/model/resource/Church.ts
@@ -2,8 +2,10 @@
class Church extends Infrastructure {
constructor (config: GameConfig) {
- super('Churches',
- `Preaching grounds for ${config.formatNumber(config.cfgCapacity.churches?.pastors ?? 0)} pastors.`);
+ super(
+ 'church',
+ 'churches',
+ `Preaching grounds for ${formatNumber(config.cfgCapacity.churches?.pastors ?? 0)} pastors.`);
this.cost.money = config.cfgInitialCost.churches;
this._costMultiplier.money = config.cfgCostMultiplier.churches;
}
diff --git a/src/model/resource/Compound.ts b/src/model/resource/Compound.ts
index f1354c0..7e9e16f 100644
--- a/src/model/resource/Compound.ts
+++ b/src/model/resource/Compound.ts
@@ -2,7 +2,9 @@
class Compound extends Infrastructure {
constructor (config: GameConfig) {
- super('Compounds',
+ super(
+ 'compound',
+ 'compounds',
'Provides space for tents, houses, and churches and a place to hide more money.');
this.cost.money = config.cfgInitialCost.compounds;
this._costMultiplier.money = config.cfgCostMultiplier.compounds;
diff --git a/src/model/resource/Credibility.ts b/src/model/resource/Credibility.ts
index f682422..6315a54 100644
--- a/src/model/resource/Credibility.ts
+++ b/src/model/resource/Credibility.ts
@@ -3,7 +3,8 @@
class Credibility extends Passive {
constructor (config: GameConfig) {
super(
- 'Credibility',
+ 'credibility',
+ 'credibilities',
'Affects your ability to recruit and retain followers.');
this.value = config.cfgPassiveMax;
}
diff --git a/src/model/resource/CryptoCurrency.ts b/src/model/resource/CryptoCurrency.ts
index 190f7b3..716b2a7 100644
--- a/src/model/resource/CryptoCurrency.ts
+++ b/src/model/resource/CryptoCurrency.ts
@@ -2,7 +2,9 @@
class CryptoCurrency extends Purchasable {
constructor (config: GameConfig) {
- super('Faithcoin',
+ super(
+ 'FaithCoin',
+ 'FaithCoins',
"A crypto coin that can't be spent directly, but provides a steady stream of passive income.");
this.cost.money = config.cfgInitialCost.cryptoCurrency;
this._costMultiplier.money = config.cfgCostMultiplier.cryptoCurrency;
diff --git a/src/model/resource/PlayerOrg.ts b/src/model/resource/Follower.ts
similarity index 87%
rename from src/model/resource/PlayerOrg.ts
rename to src/model/resource/Follower.ts
index 44e41c0..7d5c746 100644
--- a/src/model/resource/PlayerOrg.ts
+++ b/src/model/resource/Follower.ts
@@ -1,14 +1,24 @@
///
-class PlayerOrg implements IResource {
+class Follower implements IResource {
public readonly resourceType = ResourceType.religion;
- public readonly name = 'Player';
+ public readonly singularName = 'follower';
+ public readonly pluralName = 'followers';
public readonly description = 'In you they trust.';
public readonly valueInWholeNumbers = true;
- public readonly clickText = 'Recruit';
- public readonly clickDescription = 'Gather new followers.';
public value = 0;
+ public userActions: ResourceAction[] = [
+ {
+ name: 'Recruit',
+ description: 'Gather new followers.',
+ isEnabled: (state: GameState): boolean => this.value < this.max(state),
+ performAction: (state: GameState): void => {
+ this._recruitFollower(state);
+ },
+ },
+ ];
+
private _timeSinceLastLost = 0;
private _lastRecruitmentLog = 0;
private _followerSources: ResourceNumber = { };
@@ -37,27 +47,6 @@ class PlayerOrg implements IResource {
return inc;
}
- public clickAction (state: GameState): void {
- // don't exceed max
- if (this.value >= this.max(state)) {
- state.log('You have no room for more followers.');
- return;
- }
-
- // chance to fail increases as credibility decreases
- const creds = state.resource.credibility;
- if (creds?.max !== undefined) {
- const ratio = Math.ceil(creds.value) / creds.max(state);
- if (Math.random() > ratio) {
- state.log('Your recruitment efforts failed.');
- return;
- }
- }
-
- this._lastRecruitmentLog = 0; // always log on click
- this.addValue(1, state);
- }
-
public addValue (amount: number, state: GameState): void {
const oldValue = this.value;
this.value += amount;
@@ -124,12 +113,12 @@ class PlayerOrg implements IResource {
const followers = this._followerDests[rkey];
if (religion !== undefined && followers !== undefined) {
if (msg !== '') msg += ', ';
- msg += `${state.config.formatNumber(followers)} to ${religion.name}`;
+ msg += `${formatNumber(followers)} to ${religion.pluralName}`;
total += followers;
delete this._followerDests[rkey];
}
}
- state.log(`You lost ${state.config.formatNumber(total)} followers: ${msg}`);
+ state.log(`You lost ${formatNumber(total)} followers: ${msg}`);
}
if (Object.keys(this._followerSources).length > 0) {
let msg = '';
@@ -141,17 +130,38 @@ class PlayerOrg implements IResource {
if (religion !== undefined && followers !== undefined) {
if (msg !== '') msg += ', ';
msg +=
- `${state.config.formatNumber(followers)} from ${religion.name}`;
+ `${formatNumber(followers)} from ${religion.pluralName}`;
total += followers;
delete this._followerSources[rkey];
}
}
- state.log(`You gained ${state.config.formatNumber(total)} followers: ${msg}`);
+ state.log(`You gained ${formatNumber(total)} followers: ${msg}`);
}
this._lastRecruitmentLog = state.now;
}
}
+ private _recruitFollower (state: GameState): void {
+ // don't exceed max
+ if (this.value >= this.max(state)) {
+ state.log('You have no room for more followers.');
+ return;
+ }
+
+ // chance to fail increases as credibility decreases
+ const creds = state.resource.credibility;
+ if (creds?.max !== undefined) {
+ const ratio = Math.ceil(creds.value) / creds.max(state);
+ if (Math.random() > ratio) {
+ state.log('Your recruitment efforts failed.');
+ return;
+ }
+ }
+
+ this._lastRecruitmentLog = 0; // always log on click
+ this.addValue(1, state);
+ }
+
private _getRandomReligion (
state: GameState): [ResourceKey, IResource] | null {
const religs = [ResourceKey.christianity, ResourceKey.islam,
diff --git a/src/model/resource/House.ts b/src/model/resource/House.ts
index 835bdca..bb9b846 100644
--- a/src/model/resource/House.ts
+++ b/src/model/resource/House.ts
@@ -2,8 +2,10 @@
class House extends Infrastructure {
constructor (config: GameConfig) {
- super('Houses',
- `Provides room to house ${config.formatNumber(config.cfgCapacity.houses?.playerOrg ?? 0)} followers.`);
+ super(
+ 'house',
+ 'houses',
+ `Provides room to house ${formatNumber(config.cfgCapacity.houses?.playerOrg ?? 0)} followers.`);
this.cost.money = config.cfgInitialCost.houses;
this._costMultiplier.money = config.cfgCostMultiplier.houses;
}
diff --git a/src/model/resource/IResource.ts b/src/model/resource/IResource.ts
index 36a0186..681001b 100644
--- a/src/model/resource/IResource.ts
+++ b/src/model/resource/IResource.ts
@@ -1,55 +1,19 @@
-enum ResourceType {
- religion = 'religion',
- job = 'job',
- consumable = 'consumable',
- infrastructure = 'infrastructure',
- research = 'research',
- passive = 'passive',
-}
-
-enum ResourceKey {
- playerOrg = 'playerOrg',
- christianity = 'christianity',
- islam = 'islam',
- hinduism = 'hinduism',
- buddhism = 'buddhism',
- sikhism = 'sikhism',
- judaism = 'judaism',
- other = 'other',
- atheism = 'atheism',
- pastors = 'pastors',
- money = 'money',
- cryptoCurrency = 'cryptoCurrency',
- tents = 'tents',
- houses = 'houses',
- churches = 'churches',
- compounds = 'compounds',
- buildingPermit = 'buildingPermit',
- megaChurches = 'megaChurches',
- credibility = 'credibility',
-}
-
-type ResourceNumber = { [key in ResourceKey]?: number };
+///
interface IResource {
readonly resourceType: ResourceType;
- readonly name: string;
+ readonly singularName: string;
+ readonly pluralName: string;
readonly description: string;
readonly valueInWholeNumbers: boolean;
readonly value: number;
readonly cost?: ResourceNumber;
- readonly clickText?: string;
- readonly clickDescription?: string;
- // readonly altClickText?: string;
- // readonly altClickDescription?: string;
-
max?: (state: GameState) => number;
inc?: (state: GameState) => number;
- clickAction?: (state: GameState) => void;
- // altClickAction (state: GameState): void;
advanceAction?: (time: number, state: GameState) => void;
+ userActions?: ResourceAction[];
addValue: (amount: number, state: GameState) => void;
isUnlocked: (state: GameState) => boolean;
diff --git a/src/model/resource/Job.ts b/src/model/resource/Job.ts
index 77b965a..fc7b7a2 100644
--- a/src/model/resource/Job.ts
+++ b/src/model/resource/Job.ts
@@ -3,39 +3,34 @@
abstract class Job implements IResource {
public readonly resourceType = ResourceType.job;
public readonly valueInWholeNumbers = true;
- public readonly clickText = 'Hire';
- public readonly clickDescription = 'Promote one of your followers.';
public value = 0;
public readonly cost: ResourceNumber = { };
public max?: (state: GameState) => number = undefined;
public inc?: (state: GameState) => number = undefined;
+ public userActions: ResourceAction[] = [
+ {
+ name: 'Hire',
+ description: 'Promote one of your followers.',
+ isEnabled: (state: GameState): boolean =>
+ (this.max === undefined || this.value < this.max(state))
+ && this.value < this._availableJobs(state),
+ performAction: (state: GameState): void => {
+ this._promoteFollower(state);
+ },
+ },
+ ];
+
protected _costMultiplier: { [key in ResourceKey]?: number } = { };
protected _isUnlocked = false;
constructor (
- public readonly name: string,
+ public readonly singularName: string,
+ public readonly pluralName: 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.max !== undefined && this.value < this.max(state)
- && state.deductCost(this.cost)) {
- this.addValue(1);
- state.log(this._hireLog(1, state));
- for (const key in this._costMultiplier) {
- const rkey = key;
- this.cost[rkey] =
- (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1);
- }
- }
- }
-
public addValue (amount: number): void {
this.value += amount;
if (this.value < 0) this.value = 0;
@@ -81,6 +76,23 @@ abstract class Job implements IResource {
}
protected _hireLog (amount: number, _state: GameState): string {
- return `You hired ${amount} x ${this.name}.`;
+ return `You hired ${amount} x ${this.pluralName}.`;
+ }
+
+ private _promoteFollower (state: GameState): void {
+ if (this._availableJobs(state) <= 0) {
+ state.log('You have no unemployed followers to promote.');
+ return;
+ }
+ if (this.max !== undefined && this.value < this.max(state)
+ && state.deductCost(this.cost)) {
+ this.addValue(1);
+ state.log(this._hireLog(1, state));
+ for (const key in this._costMultiplier) {
+ const rkey = key;
+ this.cost[rkey] =
+ (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1);
+ }
+ }
}
}
diff --git a/src/model/resource/MegaChurch.ts b/src/model/resource/Megachurch.ts
similarity index 76%
rename from src/model/resource/MegaChurch.ts
rename to src/model/resource/Megachurch.ts
index 26f29e7..4ac019b 100644
--- a/src/model/resource/MegaChurch.ts
+++ b/src/model/resource/Megachurch.ts
@@ -1,9 +1,11 @@
///
-class MegaChurch extends Infrastructure {
+class Megachurch extends Infrastructure {
constructor (config: GameConfig) {
- super('MegaChurches',
- `Room for ${config.formatNumber(config.cfgCapacity.megaChurches?.pastors ?? 0)} pastors`);
+ super(
+ 'megachurch',
+ 'megachurches',
+ `Room for ${formatNumber(config.cfgCapacity.megaChurches?.pastors ?? 0)} pastors`);
this.cost.money = config.cfgInitialCost.megaChurches;
this._costMultiplier.money = config.cfgCostMultiplier.megaChurches;
}
diff --git a/src/model/resource/Money.ts b/src/model/resource/Money.ts
index 8654cf0..cb3bb5b 100644
--- a/src/model/resource/Money.ts
+++ b/src/model/resource/Money.ts
@@ -8,9 +8,12 @@ class Money extends Purchasable {
constructor (
public value: number
) {
- super('Money', 'Used to purchase goods and services.');
- this.clickText = 'Collect Tithes';
- this.clickDescription = 'Voluntary contributions from followers.';
+ super(
+ 'money',
+ 'moneys',
+ 'Used to purchase goods and services.',
+ 'Collect Tithes',
+ 'Voluntary contributions from followers.');
this.valueInWholeNumbers = false;
this._isUnlocked = true;
}
@@ -54,6 +57,6 @@ class Money extends Purchasable {
protected _purchaseLog (amount: number, state: GameState): string {
const followers = state.resource.playerOrg?.value ?? 0;
- return `You collected $${state.config.formatNumber(amount)} from ${state.config.formatNumber(followers)} followers.`;
+ return `You collected $${formatNumber(amount)} from ${formatNumber(followers)} followers.`;
}
}
diff --git a/src/model/resource/Passive.ts b/src/model/resource/Passive.ts
index ef3f054..841b8c0 100644
--- a/src/model/resource/Passive.ts
+++ b/src/model/resource/Passive.ts
@@ -8,7 +8,8 @@ abstract class Passive implements IResource {
public advanceAction?: (time: number, state: GameState) => void = undefined;
constructor (
- public readonly name: string,
+ public readonly singularName: string,
+ public readonly pluralName: string,
public readonly description: string
) { }
diff --git a/src/model/resource/Pastor.ts b/src/model/resource/Pastor.ts
index cdc9fc9..0178e5a 100644
--- a/src/model/resource/Pastor.ts
+++ b/src/model/resource/Pastor.ts
@@ -4,7 +4,9 @@ class Pastor extends Job {
private _timeSinceLastTithe = 0;
constructor () {
- super('Pastors',
+ super(
+ 'pastor',
+ 'pastors',
'Collect tithings for you and recruit new members from other faiths automatically.');
}
@@ -38,7 +40,7 @@ class Pastor extends Job {
collected = money.max(state) - money.value;
if (collected > 0) {
money?.addValue(collected, state);
- state.log(`Your pastors collected $${state.config.formatNumber(collected)} in tithings from ${state.config.formatNumber(tithed)} followers.`);
+ state.log(`Your pastors collected $${formatNumber(collected)} in tithings from ${formatNumber(tithed)} followers.`);
}
this._timeSinceLastTithe = 0;
}
diff --git a/src/model/resource/Purchasable.ts b/src/model/resource/Purchasable.ts
index 3195b5b..c5fc55c 100644
--- a/src/model/resource/Purchasable.ts
+++ b/src/model/resource/Purchasable.ts
@@ -3,39 +3,36 @@
abstract class Purchasable implements IResource {
public readonly resourceType: ResourceType = ResourceType.consumable;
public valueInWholeNumbers = true;
- public clickText = 'Purchase';
- public clickDescription = 'Purchase';
public value = 0;
public readonly cost: ResourceNumber = { };
public inc?: (state: GameState) => number = undefined;
public max?: (_state: GameState) => number = undefined;
+ public userActions: ResourceAction[] = [
+ {
+ name: this._purchaseButtonText,
+ description: this._purchaseDescription,
+ isEnabled: (state: GameState): boolean =>
+ (this.max === undefined || this.value < this.max(state))
+ && state.isPurchasable(this.cost),
+ performAction: (state: GameState): void => {
+ this._purchase(state);
+ },
+ },
+ ];
+
protected _costMultiplier: ResourceNumber = { };
protected _isUnlocked = false;
constructor (
- public readonly name: string,
- public readonly description: string
+ public readonly singularName: string,
+ public readonly pluralName: string,
+ public readonly description: string,
+ private readonly _purchaseButtonText: string = 'Purchase',
+ private readonly _purchaseDescription: string = `Buy a ${singularName}.`,
) { }
-
- public clickAction (state: GameState): void {
- if (this.max !== undefined && this.value >= this.max(state)) return;
- if (state.deductCost(this.cost)) {
- const amount = this._purchaseAmount(state);
- if (amount > 0) {
- this.value += amount;
- state.log(this._purchaseLog(amount, state));
- for (const key in this._costMultiplier) {
- const rkey = key;
- this.cost[rkey] =
- (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1);
- }
- }
- }
- }
-
public addValue (amount: number, _state: GameState): void {
this.value += amount;
}
@@ -56,6 +53,22 @@ abstract class Purchasable implements IResource {
}
protected _purchaseLog (amount: number, _state: GameState): string {
- return `You purchased ${amount} x ${this.name}.`;
+ return `You purchased ${amount} x ${this.pluralName}.`;
+ }
+
+ private _purchase (state: GameState): void {
+ if (this.max !== undefined && this.value >= this.max(state)) return;
+ if (state.deductCost(this.cost)) {
+ const amount = this._purchaseAmount(state);
+ if (amount > 0) {
+ this.value += amount;
+ state.log(this._purchaseLog(amount, state));
+ for (const key in this._costMultiplier) {
+ const rkey = key;
+ this.cost[rkey] =
+ (this.cost[rkey] ?? 0) * (this._costMultiplier[rkey] ?? 1);
+ }
+ }
+ }
}
}
diff --git a/src/model/resource/Religion.ts b/src/model/resource/Religion.ts
index bc32b6c..eb95c0c 100644
--- a/src/model/resource/Religion.ts
+++ b/src/model/resource/Religion.ts
@@ -5,7 +5,8 @@ class Religion implements IResource {
public readonly valueInWholeNumbers = true;
constructor (
- public readonly name: string,
+ public readonly singularName: string,
+ public readonly pluralName: string,
public readonly description: string,
public value: number,
) { }
diff --git a/src/model/resource/Research.ts b/src/model/resource/Research.ts
index 4f4567a..d303522 100644
--- a/src/model/resource/Research.ts
+++ b/src/model/resource/Research.ts
@@ -5,13 +5,17 @@ abstract class Research extends Purchasable {
public inc = undefined;
constructor (
- public readonly name: string,
+ public readonly singularName: string,
+ public readonly pluralName: string,
public readonly description: string
) {
- super(name, description);
+ super(
+ singularName,
+ pluralName,
+ description,
+ 'Learn',
+ 'Complete this research.');
this.value = 0;
- this.clickText = 'Learn';
- this.clickDescription = 'Complete this research.';
}
public max: (state: GameState) => number = (_state) => 1;
diff --git a/src/model/resource/SharedTypes.ts b/src/model/resource/SharedTypes.ts
new file mode 100644
index 0000000..471f195
--- /dev/null
+++ b/src/model/resource/SharedTypes.ts
@@ -0,0 +1,41 @@
+///
+
+enum ResourceType {
+ religion = 'religion',
+ job = 'job',
+ consumable = 'consumable',
+ infrastructure = 'infrastructure',
+ research = 'research',
+ passive = 'passive',
+}
+
+enum ResourceKey {
+ playerOrg = 'playerOrg',
+ christianity = 'christianity',
+ islam = 'islam',
+ hinduism = 'hinduism',
+ buddhism = 'buddhism',
+ sikhism = 'sikhism',
+ judaism = 'judaism',
+ other = 'other',
+ atheism = 'atheism',
+ pastors = 'pastors',
+ money = 'money',
+ cryptoCurrency = 'cryptoCurrency',
+ tents = 'tents',
+ houses = 'houses',
+ churches = 'churches',
+ compounds = 'compounds',
+ buildingPermit = 'buildingPermit',
+ megaChurches = 'megaChurches',
+ credibility = 'credibility',
+}
+
+type ResourceNumber = { [key in ResourceKey]?: number };
+
+type ResourceAction = {
+ name: string;
+ description: string;
+ isEnabled: (state: GameState) => boolean;
+ performAction: (state: GameState) => void;
+};
diff --git a/src/model/resource/Tent.ts b/src/model/resource/Tent.ts
index 86ea24b..d6d3e41 100644
--- a/src/model/resource/Tent.ts
+++ b/src/model/resource/Tent.ts
@@ -2,8 +2,10 @@
class Tent extends Infrastructure {
constructor (config: GameConfig) {
- super('Tents',
- `Provides room to house ${config.formatNumber(config.cfgCapacity.tents?.playerOrg ?? 0)} followers.`);
+ super(
+ 'tent',
+ 'tents',
+ `Provides room to house ${formatNumber(config.cfgCapacity.tents?.playerOrg ?? 0)} followers.`);
this.cost.money = config.cfgInitialCost.tents;
this._costMultiplier.money = config.cfgCostMultiplier.tents;
}
diff --git a/src/render/DebugRenderer.ts b/src/render/DebugRenderer.ts
index a16adaa..2616bc3 100644
--- a/src/render/DebugRenderer.ts
+++ b/src/render/DebugRenderer.ts
@@ -53,17 +53,21 @@ class DebugRenderer implements IRenderer {
let content = `
- ${this._escape(resource.name)}
+ ${this._escape(resource.pluralName)}
`;
- if (resource.clickText !== undefined
- && resource.clickDescription !== undefined) {
- content += `
- `;
+ if (resource.userActions !== undefined) {
+ content += '
';
+ for (let i = 0; i < resource.userActions.length; i++) {
+ const action = resource.userActions[i];
+ content += ``;
+ }
}
if (resource.cost !== undefined
&& Object.keys(resource.cost).length !== 0) {
@@ -71,11 +75,14 @@ class DebugRenderer implements IRenderer {
}
el.innerHTML = content;
resContainer.appendChild(el);
- if (resource.clickAction !== undefined) {
- const btn = el.getElementsByClassName('resource-btn')[0];
- btn.addEventListener('click', (): void => {
- state.performClick(rkey);
- });
+ if (resource.userActions !== undefined) {
+ for (let i = 0; i < resource.userActions.length; i++) {
+ const action = resource.userActions[i];
+ const btn = document.getElementById(`resource-btn-${rkey}-${i}`);
+ btn?.addEventListener('click', (): void => {
+ state.performAction(rkey, i);
+ });
+ }
}
}
// create tools footer
@@ -103,22 +110,25 @@ class DebugRenderer implements IRenderer {
const value = resource.valueInWholeNumbers
? Math.floor(resource.value)
: resource.value;
- elV.innerHTML = state.config.formatNumber(value);
+ elV.innerHTML = formatNumber(value);
elT.innerHTML = resource.max !== undefined
- ? ` / ${state.config.formatNumber(resource.max(state))}`
+ ? ` / ${formatNumber(resource.max(state))}`
: '';
- const elB = el.getElementsByClassName('resource-btn');
- if (elB.length > 0) {
- const enabled = state.isPurchasable(resource.cost)
- && (resource.max === undefined
- || resource.value < resource.max(state));
- if (enabled) elB[0].removeAttribute('disabled');
- else elB[0].setAttribute('disabled', 'disabled');
+ if (resource.userActions !== undefined) {
+ for (let i = 0; i < resource.userActions.length; i++) {
+ const elB = document.getElementById(`resource-btn-${rkey}-${i}`);
+ if (elB === null) continue;
+ if (resource.userActions[i].isEnabled(state)) {
+ elB.removeAttribute('disabled');
+ } else {
+ elB.setAttribute('disabled', 'disabled');
+ }
+ }
}
if (resource.inc !== undefined && resource.inc(state) > 0) {
const elI = el.getElementsByClassName('resource-inc')[0];
elI.innerHTML =
- ` +${state.config.formatNumber(resource.inc(state))}/s`;
+ ` +${formatNumber(resource.inc(state))}/s`;
}
if (this._handleClick) {
const elC = el.getElementsByClassName('resource-cost');
@@ -154,10 +164,10 @@ class DebugRenderer implements IRenderer {
if (resource.cost?.[rkey] !== undefined) {
if (cost !== '') cost += ', ';
if (rkey === ResourceKey.money) {
- cost += `$${state.config.formatNumber(resource.cost[rkey] ?? 0)}`;
+ cost += `$${formatNumber(resource.cost[rkey] ?? 0)}`;
} else {
- cost += `${state.config.formatNumber(resource.cost[rkey] ?? 0)}
- ${state.resource[rkey]?.name ?? rkey}`;
+ cost += `${formatNumber(resource.cost[rkey] ?? 0)}
+ ${state.resource[rkey]?.pluralName ?? rkey}`;
}
}
}