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/Church.ts" />
|
||||
/// <reference path="./resource/Compound.ts" />
|
||||
/// <reference path="./resource/CompoundManager.ts" />
|
||||
/// <reference path="./resource/Credibility.ts" />
|
||||
/// <reference path="./resource/CryptoCurrency.ts" />
|
||||
/// <reference path="./resource/CryptoMarket.ts" />
|
||||
|
@ -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));
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
|
|
|
@ -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="./Job.ts" />
|
||||
|
||||
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 = <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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ enum ResourceKey {
|
|||
other = 'other',
|
||||
atheism = 'atheism',
|
||||
pastors = 'pastors',
|
||||
compoundManagers = 'compoundManagers',
|
||||
money = 'money',
|
||||
cryptoCurrency = 'cryptoCurrency',
|
||||
cryptoMarket = 'cryptoMarket',
|
||||
|
|
|
@ -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');
|
||||
|
|
Reference in New Issue