added compound manager, followers quit their jobs when you run out of money
This commit is contained in:
parent
5fd789ca88
commit
2126ed483b
|
@ -2,6 +2,7 @@
|
||||||
/// <reference path="./resource/BuildingPermit.ts" />
|
/// <reference path="./resource/BuildingPermit.ts" />
|
||||||
/// <reference path="./resource/Church.ts" />
|
/// <reference path="./resource/Church.ts" />
|
||||||
/// <reference path="./resource/Compound.ts" />
|
/// <reference path="./resource/Compound.ts" />
|
||||||
|
/// <reference path="./resource/CompoundManager.ts" />
|
||||||
/// <reference path="./resource/Credibility.ts" />
|
/// <reference path="./resource/Credibility.ts" />
|
||||||
/// <reference path="./resource/CryptoCurrency.ts" />
|
/// <reference path="./resource/CryptoCurrency.ts" />
|
||||||
/// <reference path="./resource/CryptoMarket.ts" />
|
/// <reference path="./resource/CryptoMarket.ts" />
|
||||||
|
@ -58,12 +59,19 @@ class GameConfig {
|
||||||
};
|
};
|
||||||
|
|
||||||
public cfgSalary: ResourceNumber = {
|
public cfgSalary: ResourceNumber = {
|
||||||
pastors: 7.5,
|
pastors: 250,
|
||||||
|
compoundManagers: 1000,
|
||||||
};
|
};
|
||||||
|
|
||||||
public cfgCapacity: { [key in ResourceKey]?: ResourceNumber } = {
|
public cfgCapacity: { [key in ResourceKey]?: ResourceNumber } = {
|
||||||
churches: { pastors: 2 },
|
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 },
|
houses: { followers: 10 },
|
||||||
megaChurches: { pastors: 5 },
|
megaChurches: { pastors: 5 },
|
||||||
tents: { followers: 2 },
|
tents: { followers: 2 },
|
||||||
|
@ -78,6 +86,8 @@ class GameConfig {
|
||||||
public cfgCryptoMarketGrowthBias = 0.1;
|
public cfgCryptoMarketGrowthBias = 0.1;
|
||||||
public cfgDefaultSellMultiplier = 0.5;
|
public cfgDefaultSellMultiplier = 0.5;
|
||||||
public cfgFollowerGainLossLogTimer = 10000;
|
public cfgFollowerGainLossLogTimer = 10000;
|
||||||
|
public cfgNoMoneyQuitRate = 0.2;
|
||||||
|
public cfgNoMoneyQuitTime = 10000;
|
||||||
public cfgPassiveMax = 100;
|
public cfgPassiveMax = 100;
|
||||||
public cfgPastorRecruitRate = 0.01;
|
public cfgPastorRecruitRate = 0.01;
|
||||||
public cfgPastorTitheCollectionFollowerMax = 100;
|
public cfgPastorTitheCollectionFollowerMax = 100;
|
||||||
|
@ -184,6 +194,7 @@ class GameConfig {
|
||||||
|
|
||||||
// add jobs
|
// add jobs
|
||||||
state.addResource(ResourceKey.pastors, new Pastor());
|
state.addResource(ResourceKey.pastors, new Pastor());
|
||||||
|
state.addResource(ResourceKey.compoundManagers, new CompoundManager());
|
||||||
|
|
||||||
// add resources
|
// add resources
|
||||||
state.addResource(ResourceKey.money, new Money(3.5));
|
state.addResource(ResourceKey.money, new Money(3.5));
|
||||||
|
|
|
@ -14,10 +14,15 @@ function formatNumber(num: number): string {
|
||||||
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
|
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
|
||||||
let item: UnitLookup | undefined;
|
let item: UnitLookup | undefined;
|
||||||
for (item of lookup.slice().reverse()) {
|
for (item of lookup.slice().reverse()) {
|
||||||
if (num >= item.value) break;
|
if (Math.abs(num) >= item.value) break;
|
||||||
}
|
}
|
||||||
return item !== undefined
|
const sign = num < 0 ? '-' : '';
|
||||||
? (num / item.value).toFixed(numberFormatDigits).replace(rx, '$1') +
|
const number =
|
||||||
item.symbol
|
item !== undefined
|
||||||
: num.toFixed(numberFormatDigits).replace(rx, '$1');
|
? (Math.abs(num) / item.value)
|
||||||
|
.toFixed(numberFormatDigits)
|
||||||
|
.replace(rx, '$1') + item.symbol
|
||||||
|
: Math.abs(num).toFixed(numberFormatDigits).replace(rx, '$1');
|
||||||
|
|
||||||
|
return `${sign}${number}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
/// <reference path="./IResource.ts" />
|
/// <reference path="./IResource.ts" />
|
||||||
|
/// <reference path="./Job.ts" />
|
||||||
|
|
||||||
class Follower implements IResource {
|
class Follower implements IResource {
|
||||||
public readonly resourceType = ResourceType.religion;
|
public readonly resourceType = ResourceType.religion;
|
||||||
|
@ -24,6 +25,8 @@ class Follower implements IResource {
|
||||||
private _lastRecruitmentLog = 0;
|
private _lastRecruitmentLog = 0;
|
||||||
private _followerSources: ResourceNumber = {};
|
private _followerSources: ResourceNumber = {};
|
||||||
private _followerDests: ResourceNumber = {};
|
private _followerDests: ResourceNumber = {};
|
||||||
|
private _timeSinceLastQuit = 0;
|
||||||
|
private _quitTracker: ResourceNumber = {};
|
||||||
|
|
||||||
public max(state: GameState): number {
|
public max(state: GameState): number {
|
||||||
let max = state.config.cfgInitialMax.followers ?? 0;
|
let max = state.config.cfgInitialMax.followers ?? 0;
|
||||||
|
@ -84,7 +87,7 @@ class Follower implements IResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 if credibility < 100%
|
||||||
this._timeSinceLastLost += time;
|
this._timeSinceLastLost += time;
|
||||||
if (this._timeSinceLastLost > state.config.cfgCredibilityFollowerLossTime) {
|
if (this._timeSinceLastLost > state.config.cfgCredibilityFollowerLossTime) {
|
||||||
if (this.value > 0) {
|
if (this.value > 0) {
|
||||||
|
@ -104,12 +107,40 @@ class Follower implements IResource {
|
||||||
this._timeSinceLastLost = 0;
|
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 (
|
if (
|
||||||
state.now - this._lastRecruitmentLog >
|
state.now - this._lastRecruitmentLog >
|
||||||
state.config.cfgFollowerGainLossLogTimer &&
|
state.config.cfgFollowerGainLossLogTimer &&
|
||||||
(Object.keys(this._followerSources).length > 0 ||
|
(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) {
|
if (Object.keys(this._followerDests).length > 0) {
|
||||||
let msg = '';
|
let msg = '';
|
||||||
|
@ -155,6 +186,28 @@ class Follower implements IResource {
|
||||||
}: ${msg}`
|
}: ${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;
|
this._lastRecruitmentLog = state.now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,8 @@ abstract class Job implements IResource {
|
||||||
description: 'Promote one of your followers.',
|
description: 'Promote one of your followers.',
|
||||||
isEnabled: (state: GameState): boolean =>
|
isEnabled: (state: GameState): boolean =>
|
||||||
(this.max === undefined || this.value < this.max(state)) &&
|
(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 => {
|
performAction: (state: GameState): void => {
|
||||||
this._promoteFollower(state);
|
this._promoteFollower(state);
|
||||||
},
|
},
|
||||||
|
@ -40,6 +41,39 @@ abstract class Job implements IResource {
|
||||||
public readonly description: string
|
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 {
|
public addValue(amount: number): void {
|
||||||
this.value += amount;
|
this.value += amount;
|
||||||
if (this.value < 0) this.value = 0;
|
if (this.value < 0) this.value = 0;
|
||||||
|
@ -51,39 +85,14 @@ abstract class Job implements IResource {
|
||||||
|
|
||||||
public advanceAction(_time: number, state: GameState): void {
|
public advanceAction(_time: number, state: GameState): void {
|
||||||
// if we're out of followers then the jobs also vacate
|
// 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) {
|
if (avail < 0 && this.value > 0) {
|
||||||
this.addValue(avail);
|
this.addValue(avail);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
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 {
|
protected _hireLog(amount: number, _state: GameState): string {
|
||||||
return amount > 0
|
return amount > 0
|
||||||
? `You hired ${amount} ${
|
? `You hired ${amount} ${
|
||||||
|
@ -95,7 +104,7 @@ abstract class Job implements IResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _promoteFollower(state: GameState): void {
|
private _promoteFollower(state: GameState): void {
|
||||||
if (this._availableJobs(state) <= 0) return;
|
if (Job.availableJobs(state) <= 0) return;
|
||||||
if (
|
if (
|
||||||
this.max !== undefined &&
|
this.max !== undefined &&
|
||||||
this.value < this.max(state) &&
|
this.value < this.max(state) &&
|
||||||
|
|
|
@ -47,6 +47,10 @@ class Money implements IResource {
|
||||||
(state.resource.pastors?.value ?? 0) *
|
(state.resource.pastors?.value ?? 0) *
|
||||||
(state.config.cfgSalary.pastors ?? 0);
|
(state.config.cfgSalary.pastors ?? 0);
|
||||||
|
|
||||||
|
inc -=
|
||||||
|
(state.resource.compoundManagers?.value ?? 0) *
|
||||||
|
(state.config.cfgSalary.compoundManagers ?? 0);
|
||||||
|
|
||||||
return inc;
|
return inc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ enum ResourceKey {
|
||||||
other = 'other',
|
other = 'other',
|
||||||
atheism = 'atheism',
|
atheism = 'atheism',
|
||||||
pastors = 'pastors',
|
pastors = 'pastors',
|
||||||
|
compoundManagers = 'compoundManagers',
|
||||||
money = 'money',
|
money = 'money',
|
||||||
cryptoCurrency = 'cryptoCurrency',
|
cryptoCurrency = 'cryptoCurrency',
|
||||||
cryptoMarket = 'cryptoMarket',
|
cryptoMarket = 'cryptoMarket',
|
||||||
|
|
|
@ -131,9 +131,13 @@ class DebugRenderer implements IRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resource.inc !== undefined && resource.inc(state) > 0) {
|
const inc =
|
||||||
|
resource.inc !== undefined ? resource.inc(state) : undefined;
|
||||||
const elI = el.getElementsByClassName('resource-inc')[0];
|
const elI = el.getElementsByClassName('resource-inc')[0];
|
||||||
elI.innerHTML = ` +${formatNumber(resource.inc(state))}/s`;
|
if (inc !== undefined && inc !== 0) {
|
||||||
|
elI.innerHTML = ` ${inc > 0 ? '+' : ''}${formatNumber(inc)}/s`;
|
||||||
|
} else if (elI.innerHTML !== '') {
|
||||||
|
elI.innerHTML = '';
|
||||||
}
|
}
|
||||||
if (this._handleClick) {
|
if (this._handleClick) {
|
||||||
const elC = el.getElementsByClassName('resource-cost');
|
const elC = el.getElementsByClassName('resource-cost');
|
||||||
|
|
Reference in New Issue