Skip to content

Commit

Permalink
Merge pull request #916 from idshibanov/extract_economy_data
Browse files Browse the repository at this point in the history
Implement organisation and city economy
(Does NOT include X-COM funding)
  • Loading branch information
FilmBoy84 committed Jul 31, 2020
2 parents 3d1b769 + 21044a8 commit ae1deb1
Show file tree
Hide file tree
Showing 22 changed files with 347 additions and 38 deletions.
8 changes: 0 additions & 8 deletions data/scripts/openapoc_base.lua
Expand Up @@ -136,14 +136,6 @@ local oldUpdateEndOfWeekHook = OpenApoc.hook.updateEndOfWeek
OpenApoc.hook.updateEndOfWeek = function()
if oldUpdateEndOfWeekHook then oldUpdateEndOfWeekHook() end

FW.LogWarning('Implement economy for orgs, for now just give em cash')
for org_id, org_object in pairs(GS.organisations) do
if org_id ~= GS.player.id then
if org_object.balance < 100000 then
org_object.balance = 100000
end
end
end
updateUfoGrowth()
updateEconomy()
end
54 changes: 47 additions & 7 deletions game/state/city/building.cpp
Expand Up @@ -9,6 +9,7 @@
#include "game/state/city/vehiclemission.h"
#include "game/state/gameevent.h"
#include "game/state/gamestate.h"
#include "game/state/rules/city/scenerytiletype.h"
#include "game/state/shared/organisation.h"

// Uncomment to make cargo system output warnings
Expand All @@ -31,12 +32,12 @@ sp<BuildingFunction> StateObject<BuildingFunction>::get(const GameState &state,

template <> const UString &StateObject<BuildingFunction>::getPrefix()
{
static UString prefix = "BUILDINGFUNCTION_";
static const UString prefix = "BUILDINGFUNCTION_";
return prefix;
}
template <> const UString &StateObject<BuildingFunction>::getTypeName()
{
static UString name = "AgentType";
static const UString name = "BuildingFunction";
return name;
}

Expand Down Expand Up @@ -96,6 +97,48 @@ bool Building::hasAliens() const
return false;
}

void Building::initBuilding(GameState &state)
{
// Initialize economy data, done in the map/city editor or when game starts for the first time
// Not on save/load, that's why values are serialized
currentWage = city->civilianSalary;
maximumWorkforce = countActiveTiles() * function->workersPerTile / 2;
currentWorkforce = maximumWorkforce * 70 / 100;
maintenanceCosts = randBoundsInclusive(state.rng, 90, 110) * function->baseCost / 100;
incomePerCapita = randBoundsInclusive(state.rng, 90, 110) * function->baseIncome / 100;
investment = function->investmentValue;
prestige = function->prestige;
}

void Building::updateWorkforce()
{
maximumWorkforce = countActiveTiles() * function->workersPerTile / 2;
if (maximumWorkforce < currentWorkforce)
{
city->populationUnemployed -= currentWorkforce - maximumWorkforce;
currentWorkforce = maximumWorkforce;
}
}

int Building::calculateIncome() const
{
return currentWorkforce * (incomePerCapita - currentWage) - maintenanceCosts;
}

unsigned Building::countActiveTiles() const
{
unsigned relevantTiles = 0;
for (const auto &p : buildingParts)
{
const auto tile = city->map->getTile(p);
if (tile->presentScenery && tile->presentScenery->type->isBuildingPart)
{
relevantTiles++;
}
}
return relevantTiles;
}

void Building::updateDetection(GameState &state, unsigned int ticks)
{
if (ticksDetectionTimeOut > 0)
Expand Down Expand Up @@ -970,9 +1013,6 @@ void Building::buildingPartChange(GameState &state, Vec3<int> part, bool intact)
}
else
{
// Skin36 had some code figured out about this
// which counted score of parts and when it was below certain value
// building was considered dead
buildingParts.erase(part);
if (buildingParts.find(crewQuarters) == buildingParts.end())
{
Expand All @@ -984,7 +1024,7 @@ void Building::buildingPartChange(GameState &state, Vec3<int> part, bool intact)
agent->die(state, true);
}
}
if (!isAlive(state))
if (!isAlive())
{
if (base)
{
Expand All @@ -1009,6 +1049,6 @@ void Building::decreasePendingInvestigatorCount(GameState &state)
}
}

bool Building::isAlive(GameState &state [[maybe_unused]]) const { return !buildingParts.empty(); }
bool Building::isAlive() const { return countActiveTiles() > 0; }

} // namespace OpenApoc
25 changes: 23 additions & 2 deletions game/state/city/building.h
Expand Up @@ -37,6 +37,12 @@ class BuildingFunction : public StateObject<BuildingFunction>
{
public:
UString name;
int baseCost = 0;
int baseIncome = 0;
int workersPerTile = 0;
int agentSpawnType = 0;
int investmentValue = 0;
int prestige = 0;
int infiltrationSpeed = 0;
int detectionWeight = 0;
StateRef<UfopaediaEntry> ufopaedia_entry;
Expand All @@ -60,6 +66,17 @@ class Building : public StateObject<Building>, public std::enable_shared_from_th
std::set<StateRef<Agent>> currentAgents;
std::list<Cargo> cargo;

// Building economy data
bool isPurchesable = false;
int purchasePrice = 0;
int maintenanceCosts = 0;
int maximumWorkforce = 0;
int currentWorkforce = 0;
int incomePerCapita = 0;
int currentWage = 0;
int investment = 0;
int prestige = 0;

uint64_t timeOfLastAttackEvent = 0;
unsigned ticksDetectionTimeOut = 0;
unsigned ticksDetectionAttemptAccumulated = 0;
Expand All @@ -80,14 +97,18 @@ class Building : public StateObject<Building>, public std::enable_shared_from_th
void detect(GameState &state, bool forced = false);
void alienGrowth(GameState &state);
void alienMovement(GameState &state);
void initBuilding(GameState &state);
unsigned countActiveTiles() const;
void updateWorkforce();
int calculateIncome() const;

void underAttack(GameState &state, StateRef<Organisation> attacker);

void collapse(GameState &state);
void buildingPartChange(GameState &state, Vec3<int> part, bool intact);
bool isAlive(GameState &state) const;
bool isAlive() const;

// Following members are not serialized, but rather are set in City::initMap method
// Following members are not serialized, but rather are set in City::initCity method

Vec3<int> crewQuarters = {-1, -1, -1};
Vec3<int> carEntranceLocation = {-1, -1, -1};
Expand Down
16 changes: 15 additions & 1 deletion game/state/city/city.cpp
Expand Up @@ -70,7 +70,7 @@ City::~City()
}
}

void City::initMap(GameState &state)
void City::initCity(GameState &state)
{
if (this->map)
{
Expand Down Expand Up @@ -115,6 +115,8 @@ void City::initMap(GameState &state)
}
}
}

int subtotalIncome = 0;
for (auto &b : this->buildings)
{
if (b.second->landingPadLocations.empty())
Expand All @@ -140,7 +142,14 @@ void City::initMap(GameState &state)
spaceports.emplace_back(&state, b.first);
}
b.second->owner->buildings.emplace_back(&state, b.first);

populationWorking += b.second->currentWorkforce;
populationUnemployed += b.second->maximumWorkforce - b.second->currentWorkforce;
subtotalIncome += b.second->currentWage;
}
averageWage = (populationWorking) ? subtotalIncome / populationWorking : 0;
populationUnemployed += 500; // original adjustment

for (auto &p : this->projectiles)
{
this->map->addObjectToMap(p);
Expand Down Expand Up @@ -307,6 +316,11 @@ void City::dailyLoop(GameState &state)
if (state.cities["CITYMAP_ALIEN"] != shared_from_this())
{
repairScenery(state);
// check if employment situation changes in the building
for (auto &b : buildings)
{
b.second->updateWorkforce();
}
}
generatePortals(state);
}
Expand Down
9 changes: 7 additions & 2 deletions game/state/city/city.h
Expand Up @@ -79,7 +79,7 @@ class City : public StateObject<City>, public std::enable_shared_from_this<City>
City() = default;
~City() override;

void initMap(GameState &state);
void initCity(GameState &state);

UString id;
Vec3<int> size = {0, 0, 0};
Expand All @@ -96,6 +96,9 @@ class City : public StateObject<City>, public std::enable_shared_from_this<City>

up<TileMap> map;

// Economy: default civilian salary that setting their expectations
int civilianSalary = 0;

// Unlocks when visiting this
std::list<StateRef<ResearchTopic>> researchUnlock;

Expand Down Expand Up @@ -161,8 +164,10 @@ class City : public StateObject<City>, public std::enable_shared_from_this<City>
Vec3<float> &target, int accuracy, bool cloaked);

// Following members are not serialized, but rather are set in initCity method

std::list<StateRef<Building>> spaceports;
int populationUnemployed = 0;
int populationWorking = 0;
int averageWage = 0;
};

}; // namespace OpenApoc
2 changes: 1 addition & 1 deletion game/state/city/scenery.h
Expand Up @@ -77,7 +77,7 @@ class Scenery : public SupportedMapPart, public std::enable_shared_from_this<Sce
// Attaches to at least something nearby
bool attachToSomething();

// Following members are not serialized, but rather are set in City::initMap method
// Following members are not serialized, but rather are set in City::initCity method

sp<TileObjectScenery> tileObject;
sp<Doodad> overlayDoodad;
Expand Down
4 changes: 2 additions & 2 deletions game/state/city/vehiclemission.cpp
Expand Up @@ -1566,7 +1566,7 @@ bool VehicleMission::isFinishedInternal(GameState &state, Vehicle &v)
case MissionType::Teleport:
return true;
case MissionType::AttackBuilding:
return targetBuilding && !targetBuilding->isAlive(state);
return targetBuilding && !targetBuilding->isAlive();
default:
LogWarning("TODO: Implement isFinishedInternal");
return false;
Expand Down Expand Up @@ -2982,7 +2982,7 @@ bool VehicleMission::acquireTargetBuilding(GameState &state, Vehicle &v)
std::list<StateRef<Building>> availableBuildings;
for (auto &b : v.city->buildings)
{
if (!b.second->isAlive(state))
if (!b.second->isAlive())
{
continue;
}
Expand Down

0 comments on commit ae1deb1

Please sign in to comment.