Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement organization and city economy #916

Merged
merged 26 commits into from Jul 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
54c68c4
Expand mapping of original building struct
idshibanov Jul 28, 2020
1dad965
Add economy variables to Building class
idshibanov Jul 28, 2020
8354b24
Extract values if it's not an alien building
idshibanov Jul 28, 2020
96620d6
Merge branch 'openapoc/master' into extract_economy_data
idshibanov Jul 28, 2020
a6f8a64
Serialize additional data
idshibanov Jul 28, 2020
db92669
Initialize city data
idshibanov Jul 29, 2020
cd7d207
Add building init stub for future maps
idshibanov Jul 29, 2020
f97ff37
Add city-level weekly update call
idshibanov Jul 29, 2020
aaae5ab
Extract building initialization data
idshibanov Jul 29, 2020
b133045
Update Building::isAlive logic
idshibanov Jul 29, 2020
72d3a43
Update BuildingFunction extraction and serialization
idshibanov Jul 29, 2020
6472e48
Avoid using unsigned values
idshibanov Jul 29, 2020
5487079
Initial implementation of civilian workforce
idshibanov Jul 29, 2020
dfcd825
Implement AI organization budget management
idshibanov Jul 29, 2020
a1772aa
Update organization finances at the start of the new game
idshibanov Jul 29, 2020
5f9f8e3
Bugfixes after testing
idshibanov Jul 29, 2020
a814c6a
Formatting fix
idshibanov Jul 29, 2020
9918b91
Fix small logical errors
idshibanov Jul 29, 2020
f3a6096
Fix organization references
idshibanov Jul 29, 2020
035e704
isBuildingPart boolean determined by original data
idshibanov Jul 30, 2020
fd777b0
Merge branch 'master' of https://github.com/OpenApoc/OpenApoc.git int…
idshibanov Jul 30, 2020
59cc485
Replace static building data with extracted one
idshibanov Jul 30, 2020
a02526d
Structured bindings and constness
idshibanov Jul 31, 2020
f23a99a
Replace magic number with civilianSalary variable
idshibanov Jul 31, 2020
797278c
Use map values for buildings for original difficulty
idshibanov Jul 31, 2020
21044a8
Comment section rather than the line
idshibanov Jul 31, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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