churches and pastors

This commit is contained in:
Rudis Muiznieks 2021-08-22 11:07:49 -05:00
parent 4e1386225f
commit 03ffdf0380
18 changed files with 181 additions and 27 deletions

View File

@ -42,6 +42,9 @@ body, html {
#resource-container-religion .resource { #resource-container-religion .resource {
background-color: #ccf; background-color: #ccf;
} }
#resource-container-job .resource {
background-color: #fcf;
}
#resource-container-consumable .resource { #resource-container-consumable .resource {
background-color: #cfc; background-color: #cfc;
} }

View File

@ -29,9 +29,11 @@ function startGame (state: GameState, renderer: IRenderer): void {
const config: GameConfig = new GameConfig(); const config: GameConfig = new GameConfig();
// debug values to make the game play faster while testing // debug values to make the game play faster while testing
config.baseTitheAmount = 1000; config.cfgTitheAmount = 1000;
config.baseCryptoReturnAmount = 100; config.cfgTimeBetweenTithes = 5000;
config.baseCredibilityRestoreRate = 5; config.cfgCryptoReturnAmount = 100;
config.cfgCredibilityRestoreRate = 5;
config.cfgPastorRecruitRate = 0.5;
const renderer: IRenderer = new DebugRenderer(); const renderer: IRenderer = new DebugRenderer();
const state: GameState = config.generateState(); const state: GameState = config.generateState();

View File

@ -1,12 +1,14 @@
/// <reference path="./GameState.ts" /> /// <reference path="./GameState.ts" />
/// <reference path="./resource/Church.ts" />
/// <reference path="./resource/Compound.ts" />
/// <reference path="./resource/Credibility.ts" /> /// <reference path="./resource/Credibility.ts" />
/// <reference path="./resource/CryptoCurrency.ts" /> /// <reference path="./resource/CryptoCurrency.ts" />
/// <reference path="./resource/House.ts" />
/// <reference path="./resource/Money.ts" /> /// <reference path="./resource/Money.ts" />
/// <reference path="./resource/Pastor.ts" />
/// <reference path="./resource/PlayerOrg.ts" /> /// <reference path="./resource/PlayerOrg.ts" />
/// <reference path="./resource/Religion.ts" /> /// <reference path="./resource/Religion.ts" />
/// <reference path="./resource/Tent.ts" /> /// <reference path="./resource/Tent.ts" />
/// <reference path="./resource/House.ts" />
/// <reference path="./resource/Compound.ts" />
class GameConfig { class GameConfig {
public worldPopulation: number = 790000000; public worldPopulation: number = 790000000;
@ -21,12 +23,15 @@ class GameConfig {
public relOtherShare: number = 0.02; public relOtherShare: number = 0.02;
public relNoneShare: number = 0.16; public relNoneShare: number = 0.16;
public baseTitheAmount: number = 10; public cfgTitheAmount: number = 10;
public baseCryptoReturnAmount: number = 1; public cfgTimeBetweenTithes: number = 30000;
public baseCredibilityRestoreRate: number = 0.25; public cfgCryptoReturnAmount: number = 1;
public cfgCredibilityRestoreRate: number = 0.25;
public cfgPastorRecruitRate: number = 0.01;
public generateState (): GameState { public generateState (): GameState {
const state: GameState = new GameState(); const state: GameState = new GameState();
state.config = this;
// create player organization // create player organization
state.addResource('plorg', new PlayerOrg()); state.addResource('plorg', new PlayerOrg());
@ -64,17 +69,19 @@ class GameConfig {
'Non-Religious', 'Atheists and agnostics.', 'Non-Religious', 'Atheists and agnostics.',
this.relNoneShare * this.worldPopulation)); this.relNoneShare * this.worldPopulation));
// add jobs
state.addResource('pstor', new Pastor());
// add resources // add resources
state.addResource('money', new Money(3.50, state.addResource('money', new Money(3.50));
this.baseTitheAmount, this.baseCryptoReturnAmount));
state.addResource('crpto', new CryptoCurrency()); state.addResource('crpto', new CryptoCurrency());
state.addResource('tents', new Tent()); state.addResource('tents', new Tent());
state.addResource('house', new House()); state.addResource('house', new House());
state.addResource('cmpnd', new Compound()); state.addResource('cmpnd', new Compound());
state.addResource('chrch', new Church());
// add passive resources // add passive resources
state.addResource('creds', new Credibility( state.addResource('creds', new Credibility());
this.baseCredibilityRestoreRate));
return state; return state;
} }

View File

@ -2,6 +2,8 @@ class GameState {
private _versionMaj: number = 0; private _versionMaj: number = 0;
private _versionMin: number = 1; private _versionMin: number = 1;
public config: GameConfig;
private _timeSinceSave: number = 0; private _timeSinceSave: number = 0;
private readonly _timeBetweenSaves: number = 10000; private readonly _timeBetweenSaves: number = 10000;

View File

@ -0,0 +1,11 @@
/// <reference path="./Infrastructure.ts" />
class Church extends Infrastructure {
constructor () {
super('Churches',
'Preaching grounds for 2 pastors.');
this.cost.money = 10000;
this._costMultiplier.money = 1.01;
this._baseMax = 2;
}
}

View File

@ -11,7 +11,7 @@ class Compound extends Infrastructure {
public isUnlocked (state: GameState): boolean { public isUnlocked (state: GameState): boolean {
if (this._isUnlocked) return true; if (this._isUnlocked) return true;
const tents: IResource = state.getResource('tents'); const tents: IResource = state.getResource('tents');
if (tents.value === tents.max(state)) { if (tents.value >= 5) {
this._isUnlocked = true; this._isUnlocked = true;
} }
return this._isUnlocked; return this._isUnlocked;

View File

@ -3,8 +3,7 @@
class Credibility extends Passive { class Credibility extends Passive {
private _lastValue: number = 100; private _lastValue: number = 100;
constructor ( constructor () {
private _baseRestoreRate: number) {
super( super(
'Credibility', 'Credibility',
'Affects your ability to recruit and retain followers.', 'Affects your ability to recruit and retain followers.',
@ -16,6 +15,6 @@ class Credibility extends Passive {
} }
public inc (state: GameState): number { public inc (state: GameState): number {
return this._baseRestoreRate; return state.config.cfgCredibilityRestoreRate;
} }
} }

View File

@ -1,6 +1,8 @@
/// <reference path="./Purchasable.ts" /> /// <reference path="./Purchasable.ts" />
class CryptoCurrency extends Purchasable { class CryptoCurrency extends Purchasable {
public readonly valueInWholeNumbers: boolean = false;
constructor () { constructor () {
super('Faithcoin', super('Faithcoin',
"A crypto coin that can't be spent directly, but provides a steady stream of passive income."); "A crypto coin that can't be spent directly, but provides a steady stream of passive income.");

View File

@ -17,8 +17,8 @@ class House extends Infrastructure {
public isUnlocked (state: GameState): boolean { public isUnlocked (state: GameState): boolean {
if (this._isUnlocked) return true; if (this._isUnlocked) return true;
const tents: IResource = state.getResource('tents'); const compounds: IResource = state.getResource('cmpnd');
if (tents.value === tents.max(state)) { if (compounds.value > 0) {
this._isUnlocked = true; this._isUnlocked = true;
} }
return this._isUnlocked; return this._isUnlocked;

View File

@ -1,5 +1,6 @@
enum ResourceType { enum ResourceType {
Religion = 'religion', Religion = 'religion',
Job = 'job',
Consumable = 'consumable', Consumable = 'consumable',
Infrastructure = 'infrastructure', Infrastructure = 'infrastructure',
Passive = 'passive' Passive = 'passive'
@ -11,6 +12,7 @@ interface IResource {
resourceType: ResourceType; resourceType: ResourceType;
value: number; value: number;
valueInWholeNumbers: boolean;
clickText: string; clickText: string;
clickDescription: string; clickDescription: string;

69
src/model/resource/Job.ts Normal file
View File

@ -0,0 +1,69 @@
/// <reference path="./IResource.ts" />
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}.`;
}
}

View File

@ -5,11 +5,10 @@ class Money extends Purchasable {
public resourceType: ResourceType = ResourceType.Consumable; public resourceType: ResourceType = ResourceType.Consumable;
public cost: { [key: string]: number } = { }; public cost: { [key: string]: number } = { };
public readonly valueInWholeNumbers: boolean = false;
constructor ( constructor (
public value: number, public value: number
private readonly _baseTitheAmount: number,
private readonly _baseCryptoReturnAmount: number
) { ) {
super('Money', 'Used to purchase goods and services.'); super('Money', 'Used to purchase goods and services.');
this.clickText = 'Collect Tithes'; this.clickText = 'Collect Tithes';
@ -22,8 +21,13 @@ class Money extends Purchasable {
} }
public inc (state: GameState): number { public inc (state: GameState): number {
let inc: number = 0;
// crypto currency // crypto currency
return state.getResource('crpto').value * this._baseCryptoReturnAmount; inc += state.getResource('crpto').value
* state.config.cfgCryptoReturnAmount;
return inc;
} }
protected _purchaseAmount (state: GameState): number { protected _purchaseAmount (state: GameState): number {
@ -33,12 +37,12 @@ class Money extends Purchasable {
return 0; return 0;
} }
const diff: number = state.now - this._lastCollectionTime; const diff: number = state.now - this._lastCollectionTime;
if (diff < 30000) { if (diff < state.config.cfgTimeBetweenTithes) {
const lost: number = 30000 / diff / 3; const lost: number = state.config.cfgTimeBetweenTithes / diff / 3;
state.getResource('creds').value -= lost; state.getResource('creds').value -= lost;
} }
// each follower gives you $10 // each follower gives you $10
const tithings: number = plorg.value * this._baseTitheAmount; const tithings: number = plorg.value * state.config.cfgTitheAmount;
this._lastCollectionTime = state.now; this._lastCollectionTime = state.now;
return tithings; return tithings;
} }

View File

@ -6,6 +6,7 @@ abstract class Passive implements IResource {
public readonly clickDescription: null = null; public readonly clickDescription: null = null;
public readonly cost: null = null; public readonly cost: null = null;
public readonly clickAction: null = null; public readonly clickAction: null = null;
public readonly valueInWholeNumbers: boolean = false;
protected _baseMax: number | null; protected _baseMax: number | null;
protected _baseInc: number | null; protected _baseInc: number | null;

View File

@ -0,0 +1,37 @@
/// <reference path="./Job.ts" />
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;
}
}
}

View File

@ -6,9 +6,9 @@ class PlayerOrg implements IResource {
public readonly description: string = 'In you they trust.'; public readonly description: string = 'In you they trust.';
public cost: { [key: string]: number } = { }; public cost: { [key: string]: number } = { };
public readonly inc: null = null;
public value: number = 0; public value: number = 0;
public readonly valueInWholeNumbers: boolean = true;
public clickText: string = 'Recruit'; public clickText: string = 'Recruit';
public clickDescription: string = 'Gather new followers.'; 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 { public advanceAction (time: number, state: GameState): void {
// chance to lose some followers every 10s if credibility < 100% // chance to lose some followers every 10s if credibility < 100%
if (state.now - this._lastLostTime > 10000) { if (state.now - this._lastLostTime > 10000) {

View File

@ -3,6 +3,7 @@
abstract class Purchasable implements IResource { abstract class Purchasable implements IResource {
public readonly resourceType: ResourceType = ResourceType.Consumable; public readonly resourceType: ResourceType = ResourceType.Consumable;
public value: number = 0; public value: number = 0;
public valueInWholeNumbers: boolean = true;
public clickText: string = 'Purchase'; public clickText: string = 'Purchase';
public clickDescription: string = 'Purchase'; public clickDescription: string = 'Purchase';

View File

@ -9,6 +9,7 @@ class Religion implements IResource {
public readonly cost: null = null; public readonly cost: null = null;
public readonly max: null = null; public readonly max: null = null;
public readonly inc: null = null; public readonly inc: null = null;
public readonly valueInWholeNumbers: boolean = true;
constructor ( constructor (
public readonly name: string, public readonly name: string,

View File

@ -82,7 +82,10 @@ class DebugRenderer implements IRenderer {
el.getElementsByClassName('resource-value')[0]; el.getElementsByClassName('resource-value')[0];
const elT: Element = const elT: Element =
el.getElementsByClassName('resource-max')[0]; 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 elT.innerHTML = resource.max !== null
&& resource.max(state) !== null && resource.max(state) !== null
? ` / ${state.formatNumber(resource.max(state))}` ? ` / ${state.formatNumber(resource.max(state))}`