<commit_before>/*
 * CityManagerImplementation.cpp
 *
 *  Created on: Oct 10, 2010
 *      Author: crush
 */

#include "engine/engine.h"
#include "server/zone/managers/city/CityManager.h"
#include "server/chat/ChatManager.h"
#include "server/zone/Zone.h"
#include "server/zone/ZoneServer.h"
#include "server/zone/objects/player/PlayerObject.h"
#include "server/zone/managers/object/ObjectManager.h"
#include "server/zone/managers/structure/StructureManager.h"
#include "server/zone/managers/planet/PlanetManager.h"
#include "server/zone/managers/player/PlayerManager.h"
#include "server/zone/objects/structure/StructureObject.h"
#include "server/zone/objects/region/Region.h"
#include "server/zone/objects/region/CityRegion.h"
#include "server/zone/objects/player/sessions/CitySpecializationSession.h"
#include "server/zone/objects/player/sessions/CityTreasuryWithdrawalSession.h"
#include "server/zone/objects/player/sui/transferbox/SuiTransferBox.h"
#include "server/zone/objects/player/sui/messagebox/SuiMessageBox.h"
#include "server/zone/objects/player/sui/inputbox/SuiInputBox.h"
#include "server/zone/objects/player/sui/callbacks/CityTreasuryDepositSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityManageMilitiaSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityAddMilitiaMemberSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityRegisterSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityMayoralVoteSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityAdjustTaxSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CitySetTaxSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityToggleZoningSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityForceRankSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityForceUpdateSuiCallback.h"
#include "server/zone/objects/region/CitizenList.h"
#include "server/zone/objects/building/BuildingObject.h"
#include "TaxPayMailTask.h"
#include "server/zone/templates/tangible/SharedStructureObjectTemplate.h"
#include "server/zone/objects/player/sui/callbacks/RenameCitySuiCallback.h"


#ifndef CITY_DEBUG
#define CITY_DEBUG
#endif

CitiesAllowed CityManagerImplementation::citiesAllowedPerRank;
CitySpecializationMap CityManagerImplementation::citySpecializations;
CityTaxMap CityManagerImplementation::cityTaxes;
Vector<uint8> CityManagerImplementation::citizensPerRank;
Vector<uint16> CityManagerImplementation::radiusPerRank;
int CityManagerImplementation::cityUpdateInterval = 0;
int CityManagerImplementation::newCityGracePeriod = 0;
int CityManagerImplementation::oldCityGracePeriod = 0;
uint64 CityManagerImplementation::citySpecializationCooldown = 0;
int CityManagerImplementation::cityVotingDuration = 0;
uint64 CityManagerImplementation::treasuryWithdrawalCooldown = 0;
byte CityManagerImplementation::cityVotingCycles = 0;
byte CityManagerImplementation::cityVotingCyclesUntilLocked = 0;
int CityManagerImplementation::decorationsPerRank = 10;
int CityManagerImplementation::trainersPerRank = 3;
int CityManagerImplementation::missionTerminalsPerRank = 3;
float CityManagerImplementation::maintenanceDiscount = 1.0f;

void CityManagerImplementation::loadLuaConfig() {
	info("Loading configuration file.", true);

	Lua* lua = new Lua();
	lua->init();

	lua->runFile("scripts/managers/city_manager.lua");

	LuaObject luaObject = lua->getGlobalObject("CitiesAllowed");

	if (luaObject.isValidTable()) {
		for (int i = 1; i <= luaObject.getTableSize(); ++i) {
			LuaObject planetCitiesAllowed = luaObject.getObjectAt(i);

			if (planetCitiesAllowed.isValidTable()) {
				String planet = planetCitiesAllowed.getStringAt(1);
				LuaObject citiesAllowed = planetCitiesAllowed.getObjectAt(2);

				if (citiesAllowed.isValidTable()) {
					Vector < byte > table;

					for (int j = 1; j <= citiesAllowed.getTableSize(); ++j) {
						table.add(citiesAllowed.getIntAt(j));
					}

					citiesAllowedPerRank.put(planet, table);
				}

				citiesAllowed.pop();
			}

			planetCitiesAllowed.pop();
		}
	}

	luaObject.pop();

	luaObject = lua->getGlobalObject("CitySpecializations");
	citySpecializations.readObject(&luaObject);
	luaObject.pop();

	info("Loaded " + String::valueOf(citySpecializations.size()) + " city specializations.", true);

	luaObject = lua->getGlobalObject("CityTaxes");
	cityTaxes.readObject(&luaObject);
	luaObject.pop();

	info("Loaded " + String::valueOf(cityTaxes.size()) + " city tax rules.", true);

	//Only load the static values on the first zone.
	cityUpdateInterval = lua->getGlobalInt("CityUpdateInterval");
	newCityGracePeriod = lua->getGlobalInt("NewCityGracePeriod");
	oldCityGracePeriod = lua->getGlobalInt("OldCityGracePeriod");
	citySpecializationCooldown = lua->getGlobalLong("CitySpecializationCooldown");
	cityVotingDuration = lua->getGlobalInt("CityVotingDuration");
	treasuryWithdrawalCooldown = lua->getGlobalLong("TreasuryWithdrawalCooldown");
	cityVotingCycles = lua->getGlobalByte("CityVotingCycles");
	cityVotingCyclesUntilLocked = lua->getGlobalByte("CityVotingCyclesUntilLocked");
	decorationsPerRank = lua->getGlobalInt("DecorationsPerRank");
	trainersPerRank = lua->getGlobalInt("TrainersPerRank");
	missionTerminalsPerRank = lua->getGlobalInt("MissionTerminalsPerRank");
	maintenanceDiscount = lua->getGlobalFloat("maintenanceDiscount");

	luaObject = lua->getGlobalObject("CitizensPerRank");

	if (luaObject.isValidTable()) {
		for (int i = 1; i <= luaObject.getTableSize(); ++i)
			citizensPerRank.add(luaObject.getIntAt(i));
	}

	luaObject.pop();

	luaObject = lua->getGlobalObject("RadiusPerRank");

	if (luaObject.isValidTable()) {
		for (int i = 1; i <= luaObject.getTableSize(); ++i)
			radiusPerRank.add(luaObject.getIntAt(i));
	}

	luaObject.pop();

	delete lua;
	lua = NULL;
}

void CityManagerImplementation::loadCityRegions() {
	info("Loading any remaining city regions.", true);

	ObjectDatabaseManager* dbManager = ObjectDatabaseManager::instance();

	ObjectDatabase* cityRegionsDB = ObjectDatabaseManager::instance()->loadObjectDatabase("cityregions", true);

	if (cityRegionsDB == NULL) {
		error("Could not load the city regions database.");
		return;
	}

	int i = 0;

	try {
		ObjectDatabaseIterator iterator(cityRegionsDB);

		ObjectInputStream* objectData = new ObjectInputStream(2000);

		uint64 objectID;

		while (iterator.getNextKeyAndValue(objectID, objectData)) {
			Reference<CityRegion*> object = Core::getObjectBroker()->lookUp(objectID).castTo<CityRegion*>();

			if (object != NULL && object->getZone() != NULL) {
				++i;
				cities.put(object->getRegionName(), object);
			} else {
				error("Failed to load city region with objectid: " + String::valueOf(objectID));
			}

			objectData->clear();
		}

		delete objectData;
	} catch (DatabaseException& e) {
		error("Failed loading city regions: " + e.getMessage());
		return;
	}

	info("Loaded " + String::valueOf(cities.size()) + " player city regions.", true);
}

CityRegion* CityManagerImplementation::createCity(CreatureObject* mayor, const String& cityName, float x, float y) {
	ManagedReference<CityRegion*> city = new CityRegion(true);
	ObjectManager::instance()->persistObject(city, 1, "cityregions");

	Locker clocker(city, mayor);

	city->setCustomRegionName(cityName);
	city->setZone(mayor->getZone());
	city->setCityRank(OUTPOST);
	city->setMayorID(mayor->getObjectID());
	Region* region = city->addRegion(x, y, radiusPerRank.get(OUTPOST - 1), true);

	city->resetVotingPeriod();
	city->setAssessmentPending(true);
	city->scheduleCitizenAssessment(newCityGracePeriod * 60);
	city->rescheduleUpdateEvent(cityUpdateInterval * 60); //Minutes

	StringIdChatParameter params("city/city", "new_city_body");
	params.setTO(mayor);
	UnicodeString subject = "@city/city:new_city_subject"; // New City Established!

	ChatManager* chatManager = zoneServer->getChatManager();
	chatManager->sendMail("@city/city:new_city_from", subject, params, mayor->getFirstName(), NULL);

	cities.put(cityName, city);

	return city;
}

bool CityManagerImplementation::isCityRankCapped(const String& planetName, byte rank) {
	Vector < byte > *citiesAllowed = &citiesAllowedPerRank.get(planetName);
	byte maxAtRank = citiesAllowed->get(rank - 1);
	byte totalAtRank = 0;

	Locker _lock(_this.getReferenceUnsafeStaticCast());

	for (int i = 0; i < cities.size(); ++i) {
		CityRegion* city = cities.get(i);

		Zone* cityZone = city->getZone();

		if (cityZone == NULL || cityZone->getZoneName() != planetName)
			continue;

		if (city->getCityRank() >= rank) {
			if (++totalAtRank >= maxAtRank) {
				return true;
			}
		}
	}

	return (totalAtRank >= maxAtRank);
}

void CityManagerImplementation::sendCityReport(CreatureObject* creature, const String& planetName, byte rank) {
	Locker _lock(_this.getReferenceUnsafeStaticCast(),creature);

	Vector < byte > *citiesAllowed = &citiesAllowedPerRank.get(planetName.toLowerCase());

	if(citiesAllowed->size() == 0) {
		creature->sendSystemMessage("INVALID PLANET");
		return;
	}

	int totalCities = 0;
	int totalErroredCities = 0;

	StringBuffer report;
	report << endl << "===============================" << endl;
	report << "City Info / Planet = " << planetName.toUpperCase();

	if (rank > 0)
		report << "  Rank = " << String::valueOf(rank) << endl;
	else
		report << endl;

	report << "===================================" << endl;
	report << "City, ";

	if (rank == 0)
		report << "Rank, ";

	report << "oid, mayorid, cityhallid, regions, citizens, civicstructures, totalstructures, treasury, Loc, Next Update, next citizen Assessment (if pending)" << endl;

	for (int i = 0; i < cities.size(); ++i) {
		CityRegion* city = cities.get(i);

		Zone* cityZone = city->getZone();

		if (cityZone == NULL) {
				totalErroredCities++;
				continue;
		}

		if(cityZone->getZoneName().toLowerCase() != planetName.toLowerCase()) {
			continue;
		}

		if (rank > 0 && city->getCityRank() != rank)
			continue;

		ManagedReference<StructureObject*> cityHall = city->getCityHall();

		totalCities++;
		report << city->getRegionName();

		if (rank == 0)
			report << ", " << String::valueOf(city->getCityRank());

		report << ", " << String::valueOf(city->getObjectID())
			<< ", " << String::valueOf(city->getMayorID());

		if (cityHall != NULL) {
			report << ", " << String::valueOf(cityHall->getObjectID());
		} else {
			report << ", NULL";
		}

		report << ", " << String::valueOf(city->getRegionsCount())
			<< ", " << String::valueOf(city->getCitizenCount())
			<< ", " << String::valueOf(city->getStructuresCount())
			<< ", " << String::valueOf(city->getAllStructuresCount())
			<< ", " << String::valueOf((int)city->getCityTreasury())
			<< ", x: " << String::valueOf(city->getPositionX()) << " y:" << String::valueOf(city->getPositionY())
			<< ", " << city->getNextUpdateTime()->getFormattedTime();

		if (city->hasAssessmentPending()) {
			report << ", " << city->getNextAssessmentTime()->getFormattedTime() << endl;
		} else {
			report << endl;
		}

	}

	report << "==============================" << endl;
	report << "Total Cities: " << String::valueOf(totalCities) << endl;

	if (rank > 0) {
		byte maxAtRank = citiesAllowed->get(rank - 1);
		report << "Max at rank: " << String::valueOf(maxAtRank) << endl;
	}

	report << "Total Errored Cities (all planets & ranks): " << String::valueOf(totalErroredCities) << endl;
	report << "==============================" << endl;
	creature->sendSystemMessage(report.toString());

	ChatManager* chatManager = zoneServer->getChatManager();
	String title = "cityInfo - " + planetName.toUpperCase();
	if (rank > 0)
		title += ", Rank " + String::valueOf(rank);

	chatManager->sendMail("System", title , report.toString(), creature->getFirstName());
}

bool CityManagerImplementation::validateCityInRange(CreatureObject* creature, Zone* zone, float x, float y) {
	Vector3 testPosition(x, y, 0);

	Locker locker(_this.getReferenceUnsafeStaticCast());

	for (int i = 0; i < cities.size(); ++i) {
		CityRegion* city = cities.get(i);
		Zone* cityZone = city->getZone();

		if (cityZone == zone) {
			try {
				Vector3 position(city->getPositionX(), city->getPositionY(), 0);

				if (position.squaredDistanceTo(testPosition) < 1024 * 1024) {
					StringIdChatParameter msg("player_structure", "city_too_close");
					msg.setTO(city->getRegionName());

					creature->sendSystemMessage(msg);

					return false;
				}
			} catch (Exception& e) {
				continue;
			}
		}
	}

	return true;
}

bool CityManagerImplementation::validateCityName(const String& name) {
	Locker locker(_this.getReferenceUnsafeStaticCast());

	if (cities.contains(name) || cities.contains(name.toLowerCase()))
		return false;

	return true;
}

void CityManagerImplementation::promptCitySpecialization(CityRegion* city, CreatureObject* mayor, SceneObject* terminal) {

	ManagedReference<CitySpecializationSession*> session = new CitySpecializationSession(mayor, city, terminal);
	mayor->addActiveSession(SessionFacadeType::CITYSPEC, session);
	session->initializeSession();
}

void CityManagerImplementation::changeCitySpecialization(CityRegion* city, CreatureObject* mayor, const String& spec) {
	Locker _clock(city, mayor);

	city->setCitySpecialization(spec);

	PlayerObject* ghost = mayor->getPlayerObject().get();

	if (ghost != NULL && !ghost->isPrivileged())
		mayor->addCooldown("city_specialization", citySpecializationCooldown); //1 week.

	StringIdChatParameter params("city/city", "spec_set"); //The city's specialization has been set to %TO.

	if (spec.isEmpty())
		params.setTO("@city/city:null");
	else
		params.setTO(spec);

	mayor->sendSystemMessage(params);

	//Resetting the city radius will remove it and reinsert it, updating it to everything in the area.
	city->setRadius(city->getRadius());
}

void CityManagerImplementation::sendStatusReport(CityRegion* city, CreatureObject* creature, SceneObject* terminal) {
	/* Status Report - Lists basic information about the city.
	 * Shows the city name, current Mayor, the waypoint for the city,
	 * the city radius, number of citizens, number of structures in the city, the city specialization,
	 * tax information, and the travel cost.
	 */

	ManagedReference<SuiListBox*> list = new SuiListBox(creature, SuiWindowType::CITY_STATUS_REPORT, 0x00);
	list->setPromptTitle("@city/city:city_info_t"); //City Status Report
	list->setPromptText("@city/city:city_info_d"); //A report of the current city follows.
	list->setUsingObject(terminal);
	list->setForceCloseDistance(16.f);

	ManagedReference<SceneObject*> mayor = zoneServer->getObject(city->getMayorID());

	list->addMenuItem("@city/city:name_prompt " + city->getRegionName()); //Name:

	if (mayor != NULL) {
		list->addMenuItem("@city/city:mayor_prompt " + mayor->getDisplayedName()); //Mayor:
	} else {
		list->addMenuItem("@city/city:mayor_prompt @city/city:null"); // Mayor: None
	}

	StringBuffer location;
	location << city->getPositionX() << " " << city->getPositionY();

	list->addMenuItem("@city/city:location_prompt " + location.toString()); //Location:
	list->addMenuItem("@city/city:radius_prompt " + String::valueOf(city->getRadius())); //Radius:
	list->addMenuItem("@city/city:citizen_prompt " + String::valueOf(city->getCitizenCount())); //Citizenship:
	list->addMenuItem("@city/city:structures_prompt " + String::valueOf(city->getAllStructuresCount())); //Structures:

	if (city->getCitySpecialization().isEmpty())
		list->addMenuItem("@city/city:specialization_prompt @city/city:null"); //Specialization: None
	else
		list->addMenuItem("@city/city:specialization_prompt " + city->getCitySpecialization()); //Specialization:

	list->addMenuItem("@city/city:current_trainers " + String::valueOf(city->getSkillTrainerCount())); // Current Trainer Count:
	list->addMenuItem("@city/city:current_mt " + String::valueOf(city->getMissionTerminalCount())); // Current Terminal Count:

	for (int i = 0; i < cityTaxes.size(); ++i) {
		CityTax* cityTax = &cityTaxes.get(i);
		int tax = city->getTax(i);
		String prompt = cityTax->getStatusPrompt();

		if (prompt.isEmpty())
			continue;

		if (prompt == "@city/city:travel_cost_prompt" && !city->hasShuttleInstallation()) {
			prompt += "_none";
			list->addMenuItem(prompt);
		} else {
			list->addMenuItem(prompt + " " + String::valueOf(tax));
		}
	}

	creature->sendMessage(list->generateMessage());
}

void CityManagerImplementation::sendStructureReport(CityRegion* city, CreatureObject* creature, SceneObject* terminal) {
	PlayerObject* ghost = creature->getPlayerObject();

	if (ghost == NULL)
		return;

	ManagedReference<SuiListBox*> maintList = new SuiListBox(creature, SuiWindowType::CITY_TREASURY_REPORT);
	maintList->setPromptTitle("@city/city:structure_list_t"); // City Structure Report
	maintList->setPromptText("@city/city:structure_list_d");

	for (int i = 0; i < city->getStructuresCount(); i++) {
		ManagedReference<StructureObject*> structure = city->getCivicStructure(i);
		if (structure != NULL)
			maintList->addMenuItem(structure->getObjectName()->getFullPath() + " - Condition : "
					+ String::valueOf((1.0f * structure->getMaxCondition() - structure->getConditionDamage())
							/ structure->getMaxCondition() * 100) + "%", i);

	}

	for (int i = 0; i < city->getDecorationCount(); i++) {
		ManagedReference<StructureObject*> deco = cast<StructureObject*> (city->getCityDecoration(i));

		if (deco != NULL)
			maintList->addMenuItem(deco->getObjectName()->getFullPath() + " - Condition : "
					+ String::valueOf((1.0f * deco->getMaxCondition() - deco->getConditionDamage())
							/ deco->getMaxCondition() * 100) + "%", i);
	}

	ghost->addSuiBox(maintList);
	creature->sendMessage(maintList->generateMessage());
}

void CityManagerImplementation::promptWithdrawCityTreasury(CityRegion* city, CreatureObject* mayor, SceneObject* terminal) {
	if (!city->isMayor(mayor->getObjectID()))
		return;

	if (!mayor->checkCooldownRecovery("city_withdrawal") && !mayor->getPlayerObject()->isPrivileged()) {
		mayor->sendSystemMessage("@city/city:withdraw_daily"); //You may only withdraw from the city treasury once per day.
		return;
	}

	ManagedReference<CityTreasuryWithdrawalSession*> session = new CityTreasuryWithdrawalSession(mayor, city, terminal);
	mayor->addActiveSession(SessionFacadeType::CITYWITHDRAW, session);
	session->initializeSession();
}

void CityManagerImplementation::withdrawFromCityTreasury(CityRegion* city, CreatureObject* mayor, int value, const String& reason, SceneObject* terminal) {
	/**
	 string/en/city/city.stf	264	treasury_withdraw_from	City Treasury Authority
	 string/en/city/city.stf	265	treasury_withdraw_subject	City Treasury Withdrawal
	 string/en/city/city.stf	266	treasury_withdraw_body	Attention! Mayor %TO has made a withdrawal from the City Treasury. Amount: %DI Reason: %TT - Treasury Authority
	 */

	Locker locker(mayor);
	Locker clocker(city, mayor);

	int minWithdrawal = city->getMinWithdrawal();

	if (city->getCityTreasury() < minWithdrawal) {
		mayor->sendSystemMessage("@city/city:no_money"); //There isn't any money to transfer!
		return;
	}

	ManagedReference<CityTreasuryWithdrawalSession*> session = mayor->getActiveSession(SessionFacadeType::CITYWITHDRAW).castTo<CityTreasuryWithdrawalSession*>();

	if (session == NULL) {
		mayor->sendSystemMessage("@city/city:withdraw_treasury_error"); // Unable to complete withdraw. Please try again.
		return;
	}

	if (!mayor->checkCooldownRecovery("city_withdrawal") && !mayor->getPlayerObject()->isPrivileged()) {
		mayor->sendSystemMessage("@city/city:withdraw_daily"); //You may only withdraw from the city treasury once per day.
		session->cancelSession();
		return;
	}

	if (value < 0) {
		mayor->sendSystemMessage("@city/city:withdraw_treasury"); //You must select a positive amount to withdraw from the treasury.
		session->cancelSession();
		return;
	}

	int maxWithdrawal = MIN(city->getMaxWithdrawal(), city->getCityTreasury());

	if (value > maxWithdrawal || value < minWithdrawal) {
		mayor->sendSystemMessage("@city/city:withdraw_limits"); // You may only withdraw between 10,000 and 50,000 credits per day.
		session->cancelSession();
		return;
	}

	mayor->addBankCredits(value, true);
	city->subtractFromCityTreasury(value);

	mayor->addCooldown("city_withdrawal", CityManagerImplementation::treasuryWithdrawalCooldown);

	session->cancelSession();

	StringIdChatParameter message("@new_player:credits_reward"); // %DI credits have been deposited into your bank account.
	message.setDI(value);
	mayor->sendSystemMessage(message);

	StringIdChatParameter emailBody("@city/city:treasury_withdraw_body");
	emailBody.setDI(value);
	emailBody.setTO(mayor);
	emailBody.setTT(reason);

	sendMail(city, "@city/city:treasury_withdraw_from", "@city/city:treasury_withdraw_subject", emailBody, NULL);
}

void CityManagerImplementation::promptDepositCityTreasury(CityRegion* city, CreatureObject* creature, SceneObject* terminal) {
	PlayerObject* ghost = creature->getPlayerObject();

	if (ghost == NULL)
		return;

	ManagedReference<SuiTransferBox*> transfer = new SuiTransferBox(creature, SuiWindowType::CITY_TREASURY_DEPOSIT);
	transfer->setPromptTitle("@city/city:treasury_deposit"); //Treasury Deposit
	transfer->setPromptText("@city/city:treasury_deposit_d"); //Enter the amount you would like to transfer to the city treasury.
	transfer->addFrom("@city/city:funds", String::valueOf(creature->getCashCredits()), String::valueOf(creature->getCashCredits()), "1");
	transfer->addTo("@city/city:treasury", "0", "0", "1");
	transfer->setUsingObject(terminal);
	transfer->setForceCloseDistance(16.f);
	transfer->setCallback(new CityTreasuryDepositSuiCallback(zoneServer, city));

	ghost->addSuiBox(transfer);
	creature->sendMessage(transfer->generateMessage());
}

void CityManagerImplementation::depositToCityTreasury(CityRegion* city, CreatureObject* creature, int amount) {
	int cash = creature->getCashCredits();

	int total = cash - amount;

	if (total < 1 || total > cash) {
		creature->sendSystemMessage("@city/city:positive_deposit"); //You must select a positive amount to transfer to the treasury.
		return;
	}

	double currentTreasury = city->getCityTreasury();

	if ((int)currentTreasury + total > 100000000) {
		creature->sendSystemMessage("The maximum treasury a city can have is 100.000.000");
		return;
	}

	city->addToCityTreasury(total);
	creature->subtractCashCredits(total);

	StringIdChatParameter params("city/city", "deposit_treasury"); //You deposit %DI credits into the treasury.
	params.setDI(total);
	creature->sendSystemMessage(params);
}

void CityManagerImplementation::sendCitizenshipReport(CityRegion* city, CreatureObject* creature, SceneObject* terminal) {
	PlayerObject* ghost = creature->getPlayerObject();

	if (ghost == NULL)
		return;

	//Check if they already have the window open.
	if (ghost->hasSuiBoxWindowType(SuiWindowType::CITY_CITIZEN_REPORT))
		return;

	ManagedReference<SuiListBox*> listbox = new SuiListBox(creature, SuiWindowType::CITY_CITIZEN_REPORT);
	listbox->setPromptTitle("@city/city:citizen_list_t"); //City Citizenship Report
	listbox->setPromptText("@city/city:citizen_list_d"); //The following is a list of the currently declared citizens (residents) of this city. All citizens are elligible to vote.
	listbox->setUsingObject(terminal);
	listbox->setForceCloseDistance(16.f);
	listbox->addMenuItem("@city/city:reg_citizen_prompt"); // Registered Citizens:

	CitizenList* citizenList = city->getCitizenList();

	for (int i = 0; i < citizenList->size(); ++i) {
		ManagedReference<SceneObject*> citizen = zoneServer->getObject(citizenList->get(i));

		if (citizen != NULL) {
			String name = "@city/city:default \t" + citizen->getDisplayedName();

			if (city->isMilitiaMember(citizen->getObjectID()))
				name += " @city/city:militia_suffix"; // (Militia)

			listbox->addMenuItem(name);
		}
	}

	ghost->addSuiBox(listbox);
	creature->sendMessage(listbox->generateMessage());
}

void CityManagerImplementation::assessCitizens(CityRegion* city) {
	Locker locker(city);

	if (zoneServer->isServerLoading()) {
		city->scheduleCitizenAssessment(10);
		return;
	}

	info("Assessing city citizens: " + city->getRegionName(), true);

	city->cleanupCitizens();

	int citizens = city->getCitizenCount();

	ChatManager* chatManager = zoneServer->getChatManager();

	ManagedReference<SceneObject*> obj = zoneServer->getObject(city->getMayorID());
	CreatureObject* mayor = NULL;

	if (obj != NULL && obj->isPlayerCreature()) {
		mayor = cast<CreatureObject*> (obj.get());
	}

	if (citizens < citizensPerRank.get(0)) {
		if (mayor != NULL) {
			StringIdChatParameter params("city/city", "new_city_fail_body");
			UnicodeString subject = "@city/city:new_city_fail_subject"; // Zoning Permits Rejected

			chatManager->sendMail("@city/city:new_city_from", subject, params, mayor->getFirstName(), NULL);
		}

		ManagedReference<StructureObject*> cityhall = city->getCityHall();

		if (cityhall != NULL) {
			StructureManager::instance()->destroyStructure(cityhall);
		} else {
			locker.release();

			destroyCity(city);
		}

	} else {
		if (mayor != NULL) {
			StringIdChatParameter params("city/city", "new_city_success_body");
			UnicodeString subject = "@city/city:new_city_success_subject"; // New City Approved!

			chatManager->sendMail("@city/city:new_city_from", subject, params, mayor->getFirstName(), NULL);
		}

		city->setAssessmentPending(false);
	}
}

void CityManagerImplementation::processCityUpdate(CityRegion* city) {
	info("Processing city update: " + city->getRegionName(), true);

	int cityRank = 0;
	float radius = 0;

	try {
		cityRank = city->getCityRank();<commit_msg>[Added] extra check to city updates to destroy cities without a city hall<commit_after>/*
 * CityManagerImplementation.cpp
 *
 *  Created on: Oct 10, 2010
 *      Author: crush
 */

#include "engine/engine.h"
#include "server/zone/managers/city/CityManager.h"
#include "server/chat/ChatManager.h"
#include "server/zone/Zone.h"
#include "server/zone/ZoneServer.h"
#include "server/zone/objects/player/PlayerObject.h"
#include "server/zone/managers/object/ObjectManager.h"
#include "server/zone/managers/structure/StructureManager.h"
#include "server/zone/managers/planet/PlanetManager.h"
#include "server/zone/managers/player/PlayerManager.h"
#include "server/zone/objects/structure/StructureObject.h"
#include "server/zone/objects/region/Region.h"
#include "server/zone/objects/region/CityRegion.h"
#include "server/zone/objects/player/sessions/CitySpecializationSession.h"
#include "server/zone/objects/player/sessions/CityTreasuryWithdrawalSession.h"
#include "server/zone/objects/player/sui/transferbox/SuiTransferBox.h"
#include "server/zone/objects/player/sui/messagebox/SuiMessageBox.h"
#include "server/zone/objects/player/sui/inputbox/SuiInputBox.h"
#include "server/zone/objects/player/sui/callbacks/CityTreasuryDepositSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityManageMilitiaSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityAddMilitiaMemberSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityRegisterSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityMayoralVoteSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityAdjustTaxSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CitySetTaxSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityToggleZoningSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityForceRankSuiCallback.h"
#include "server/zone/objects/player/sui/callbacks/CityForceUpdateSuiCallback.h"
#include "server/zone/objects/region/CitizenList.h"
#include "server/zone/objects/building/BuildingObject.h"
#include "TaxPayMailTask.h"
#include "server/zone/templates/tangible/SharedStructureObjectTemplate.h"
#include "server/zone/objects/player/sui/callbacks/RenameCitySuiCallback.h"


#ifndef CITY_DEBUG
#define CITY_DEBUG
#endif

CitiesAllowed CityManagerImplementation::citiesAllowedPerRank;
CitySpecializationMap CityManagerImplementation::citySpecializations;
CityTaxMap CityManagerImplementation::cityTaxes;
Vector<uint8> CityManagerImplementation::citizensPerRank;
Vector<uint16> CityManagerImplementation::radiusPerRank;
int CityManagerImplementation::cityUpdateInterval = 0;
int CityManagerImplementation::newCityGracePeriod = 0;
int CityManagerImplementation::oldCityGracePeriod = 0;
uint64 CityManagerImplementation::citySpecializationCooldown = 0;
int CityManagerImplementation::cityVotingDuration = 0;
uint64 CityManagerImplementation::treasuryWithdrawalCooldown = 0;
byte CityManagerImplementation::cityVotingCycles = 0;
byte CityManagerImplementation::cityVotingCyclesUntilLocked = 0;
int CityManagerImplementation::decorationsPerRank = 10;
int CityManagerImplementation::trainersPerRank = 3;
int CityManagerImplementation::missionTerminalsPerRank = 3;
float CityManagerImplementation::maintenanceDiscount = 1.0f;

void CityManagerImplementation::loadLuaConfig() {
	info("Loading configuration file.", true);

	Lua* lua = new Lua();
	lua->init();

	lua->runFile("scripts/managers/city_manager.lua");

	LuaObject luaObject = lua->getGlobalObject("CitiesAllowed");

	if (luaObject.isValidTable()) {
		for (int i = 1; i <= luaObject.getTableSize(); ++i) {
			LuaObject planetCitiesAllowed = luaObject.getObjectAt(i);

			if (planetCitiesAllowed.isValidTable()) {
				String planet = planetCitiesAllowed.getStringAt(1);
				LuaObject citiesAllowed = planetCitiesAllowed.getObjectAt(2);

				if (citiesAllowed.isValidTable()) {
					Vector < byte > table;

					for (int j = 1; j <= citiesAllowed.getTableSize(); ++j) {
						table.add(citiesAllowed.getIntAt(j));
					}

					citiesAllowedPerRank.put(planet, table);
				}

				citiesAllowed.pop();
			}

			planetCitiesAllowed.pop();
		}
	}

	luaObject.pop();

	luaObject = lua->getGlobalObject("CitySpecializations");
	citySpecializations.readObject(&luaObject);
	luaObject.pop();

	info("Loaded " + String::valueOf(citySpecializations.size()) + " city specializations.", true);

	luaObject = lua->getGlobalObject("CityTaxes");
	cityTaxes.readObject(&luaObject);
	luaObject.pop();

	info("Loaded " + String::valueOf(cityTaxes.size()) + " city tax rules.", true);

	//Only load the static values on the first zone.
	cityUpdateInterval = lua->getGlobalInt("CityUpdateInterval");
	newCityGracePeriod = lua->getGlobalInt("NewCityGracePeriod");
	oldCityGracePeriod = lua->getGlobalInt("OldCityGracePeriod");
	citySpecializationCooldown = lua->getGlobalLong("CitySpecializationCooldown");
	cityVotingDuration = lua->getGlobalInt("CityVotingDuration");
	treasuryWithdrawalCooldown = lua->getGlobalLong("TreasuryWithdrawalCooldown");
	cityVotingCycles = lua->getGlobalByte("CityVotingCycles");
	cityVotingCyclesUntilLocked = lua->getGlobalByte("CityVotingCyclesUntilLocked");
	decorationsPerRank = lua->getGlobalInt("DecorationsPerRank");
	trainersPerRank = lua->getGlobalInt("TrainersPerRank");
	missionTerminalsPerRank = lua->getGlobalInt("MissionTerminalsPerRank");
	maintenanceDiscount = lua->getGlobalFloat("maintenanceDiscount");

	luaObject = lua->getGlobalObject("CitizensPerRank");

	if (luaObject.isValidTable()) {
		for (int i = 1; i <= luaObject.getTableSize(); ++i)
			citizensPerRank.add(luaObject.getIntAt(i));
	}

	luaObject.pop();

	luaObject = lua->getGlobalObject("RadiusPerRank");

	if (luaObject.isValidTable()) {
		for (int i = 1; i <= luaObject.getTableSize(); ++i)
			radiusPerRank.add(luaObject.getIntAt(i));
	}

	luaObject.pop();

	delete lua;
	lua = NULL;
}

void CityManagerImplementation::loadCityRegions() {
	info("Loading any remaining city regions.", true);

	ObjectDatabaseManager* dbManager = ObjectDatabaseManager::instance();

	ObjectDatabase* cityRegionsDB = ObjectDatabaseManager::instance()->loadObjectDatabase("cityregions", true);

	if (cityRegionsDB == NULL) {
		error("Could not load the city regions database.");
		return;
	}

	int i = 0;

	try {
		ObjectDatabaseIterator iterator(cityRegionsDB);

		ObjectInputStream* objectData = new ObjectInputStream(2000);

		uint64 objectID;

		while (iterator.getNextKeyAndValue(objectID, objectData)) {
			Reference<CityRegion*> object = Core::getObjectBroker()->lookUp(objectID).castTo<CityRegion*>();

			if (object != NULL && object->getZone() != NULL) {
				++i;
				cities.put(object->getRegionName(), object);
			} else {
				error("Failed to load city region with objectid: " + String::valueOf(objectID));
			}

			objectData->clear();
		}

		delete objectData;
	} catch (DatabaseException& e) {
		error("Failed loading city regions: " + e.getMessage());
		return;
	}

	info("Loaded " + String::valueOf(cities.size()) + " player city regions.", true);
}

CityRegion* CityManagerImplementation::createCity(CreatureObject* mayor, const String& cityName, float x, float y) {
	ManagedReference<CityRegion*> city = new CityRegion(true);
	ObjectManager::instance()->persistObject(city, 1, "cityregions");

	Locker clocker(city, mayor);

	city->setCustomRegionName(cityName);
	city->setZone(mayor->getZone());
	city->setCityRank(OUTPOST);
	city->setMayorID(mayor->getObjectID());
	Region* region = city->addRegion(x, y, radiusPerRank.get(OUTPOST - 1), true);

	city->resetVotingPeriod();
	city->setAssessmentPending(true);
	city->scheduleCitizenAssessment(newCityGracePeriod * 60);
	city->rescheduleUpdateEvent(cityUpdateInterval * 60); //Minutes

	StringIdChatParameter params("city/city", "new_city_body");
	params.setTO(mayor);
	UnicodeString subject = "@city/city:new_city_subject"; // New City Established!

	ChatManager* chatManager = zoneServer->getChatManager();
	chatManager->sendMail("@city/city:new_city_from", subject, params, mayor->getFirstName(), NULL);

	cities.put(cityName, city);

	return city;
}

bool CityManagerImplementation::isCityRankCapped(const String& planetName, byte rank) {
	Vector < byte > *citiesAllowed = &citiesAllowedPerRank.get(planetName);
	byte maxAtRank = citiesAllowed->get(rank - 1);
	byte totalAtRank = 0;

	Locker _lock(_this.getReferenceUnsafeStaticCast());

	for (int i = 0; i < cities.size(); ++i) {
		CityRegion* city = cities.get(i);

		Zone* cityZone = city->getZone();

		if (cityZone == NULL || cityZone->getZoneName() != planetName)
			continue;

		if (city->getCityRank() >= rank) {
			if (++totalAtRank >= maxAtRank) {
				return true;
			}
		}
	}

	return (totalAtRank >= maxAtRank);
}

void CityManagerImplementation::sendCityReport(CreatureObject* creature, const String& planetName, byte rank) {
	Locker _lock(_this.getReferenceUnsafeStaticCast(),creature);

	Vector < byte > *citiesAllowed = &citiesAllowedPerRank.get(planetName.toLowerCase());

	if(citiesAllowed->size() == 0) {
		creature->sendSystemMessage("INVALID PLANET");
		return;
	}

	int totalCities = 0;
	int totalErroredCities = 0;

	StringBuffer report;
	report << endl << "===============================" << endl;
	report << "City Info / Planet = " << planetName.toUpperCase();

	if (rank > 0)
		report << "  Rank = " << String::valueOf(rank) << endl;
	else
		report << endl;

	report << "===================================" << endl;
	report << "City, ";

	if (rank == 0)
		report << "Rank, ";

	report << "oid, mayorid, cityhallid, regions, citizens, civicstructures, totalstructures, treasury, Loc, Next Update, next citizen Assessment (if pending)" << endl;

	for (int i = 0; i < cities.size(); ++i) {
		CityRegion* city = cities.get(i);

		Zone* cityZone = city->getZone();

		if (cityZone == NULL) {
				totalErroredCities++;
				continue;
		}

		if(cityZone->getZoneName().toLowerCase() != planetName.toLowerCase()) {
			continue;
		}

		if (rank > 0 && city->getCityRank() != rank)
			continue;

		ManagedReference<StructureObject*> cityHall = city->getCityHall();

		totalCities++;
		report << city->getRegionName();

		if (rank == 0)
			report << ", " << String::valueOf(city->getCityRank());

		report << ", " << String::valueOf(city->getObjectID())
			<< ", " << String::valueOf(city->getMayorID());

		if (cityHall != NULL) {
			report << ", " << String::valueOf(cityHall->getObjectID());
		} else {
			report << ", NULL";
		}

		report << ", " << String::valueOf(city->getRegionsCount())
			<< ", " << String::valueOf(city->getCitizenCount())
			<< ", " << String::valueOf(city->getStructuresCount())
			<< ", " << String::valueOf(city->getAllStructuresCount())
			<< ", " << String::valueOf((int)city->getCityTreasury())
			<< ", x: " << String::valueOf(city->getPositionX()) << " y:" << String::valueOf(city->getPositionY())
			<< ", " << city->getNextUpdateTime()->getFormattedTime();

		if (city->hasAssessmentPending()) {
			report << ", " << city->getNextAssessmentTime()->getFormattedTime() << endl;
		} else {
			report << endl;
		}

	}

	report << "==============================" << endl;
	report << "Total Cities: " << String::valueOf(totalCities) << endl;

	if (rank > 0) {
		byte maxAtRank = citiesAllowed->get(rank - 1);
		report << "Max at rank: " << String::valueOf(maxAtRank) << endl;
	}

	report << "Total Errored Cities (all planets & ranks): " << String::valueOf(totalErroredCities) << endl;
	report << "==============================" << endl;
	creature->sendSystemMessage(report.toString());

	ChatManager* chatManager = zoneServer->getChatManager();
	String title = "cityInfo - " + planetName.toUpperCase();
	if (rank > 0)
		title += ", Rank " + String::valueOf(rank);

	chatManager->sendMail("System", title , report.toString(), creature->getFirstName());
}

bool CityManagerImplementation::validateCityInRange(CreatureObject* creature, Zone* zone, float x, float y) {
	Vector3 testPosition(x, y, 0);

	Locker locker(_this.getReferenceUnsafeStaticCast());

	for (int i = 0; i < cities.size(); ++i) {
		CityRegion* city = cities.get(i);
		Zone* cityZone = city->getZone();

		if (cityZone == zone) {
			try {
				Vector3 position(city->getPositionX(), city->getPositionY(), 0);

				if (position.squaredDistanceTo(testPosition) < 1024 * 1024) {
					StringIdChatParameter msg("player_structure", "city_too_close");
					msg.setTO(city->getRegionName());

					creature->sendSystemMessage(msg);

					return false;
				}
			} catch (Exception& e) {
				continue;
			}
		}
	}

	return true;
}

bool CityManagerImplementation::validateCityName(const String& name) {
	Locker locker(_this.getReferenceUnsafeStaticCast());

	if (cities.contains(name) || cities.contains(name.toLowerCase()))
		return false;

	return true;
}

void CityManagerImplementation::promptCitySpecialization(CityRegion* city, CreatureObject* mayor, SceneObject* terminal) {

	ManagedReference<CitySpecializationSession*> session = new CitySpecializationSession(mayor, city, terminal);
	mayor->addActiveSession(SessionFacadeType::CITYSPEC, session);
	session->initializeSession();
}

void CityManagerImplementation::changeCitySpecialization(CityRegion* city, CreatureObject* mayor, const String& spec) {
	Locker _clock(city, mayor);

	city->setCitySpecialization(spec);

	PlayerObject* ghost = mayor->getPlayerObject().get();

	if (ghost != NULL && !ghost->isPrivileged())
		mayor->addCooldown("city_specialization", citySpecializationCooldown); //1 week.

	StringIdChatParameter params("city/city", "spec_set"); //The city's specialization has been set to %TO.

	if (spec.isEmpty())
		params.setTO("@city/city:null");
	else
		params.setTO(spec);

	mayor->sendSystemMessage(params);

	//Resetting the city radius will remove it and reinsert it, updating it to everything in the area.
	city->setRadius(city->getRadius());
}

void CityManagerImplementation::sendStatusReport(CityRegion* city, CreatureObject* creature, SceneObject* terminal) {
	/* Status Report - Lists basic information about the city.
	 * Shows the city name, current Mayor, the waypoint for the city,
	 * the city radius, number of citizens, number of structures in the city, the city specialization,
	 * tax information, and the travel cost.
	 */

	ManagedReference<SuiListBox*> list = new SuiListBox(creature, SuiWindowType::CITY_STATUS_REPORT, 0x00);
	list->setPromptTitle("@city/city:city_info_t"); //City Status Report
	list->setPromptText("@city/city:city_info_d"); //A report of the current city follows.
	list->setUsingObject(terminal);
	list->setForceCloseDistance(16.f);

	ManagedReference<SceneObject*> mayor = zoneServer->getObject(city->getMayorID());

	list->addMenuItem("@city/city:name_prompt " + city->getRegionName()); //Name:

	if (mayor != NULL) {
		list->addMenuItem("@city/city:mayor_prompt " + mayor->getDisplayedName()); //Mayor:
	} else {
		list->addMenuItem("@city/city:mayor_prompt @city/city:null"); // Mayor: None
	}

	StringBuffer location;
	location << city->getPositionX() << " " << city->getPositionY();

	list->addMenuItem("@city/city:location_prompt " + location.toString()); //Location:
	list->addMenuItem("@city/city:radius_prompt " + String::valueOf(city->getRadius())); //Radius:
	list->addMenuItem("@city/city:citizen_prompt " + String::valueOf(city->getCitizenCount())); //Citizenship:
	list->addMenuItem("@city/city:structures_prompt " + String::valueOf(city->getAllStructuresCount())); //Structures:

	if (city->getCitySpecialization().isEmpty())
		list->addMenuItem("@city/city:specialization_prompt @city/city:null"); //Specialization: None
	else
		list->addMenuItem("@city/city:specialization_prompt " + city->getCitySpecialization()); //Specialization:

	list->addMenuItem("@city/city:current_trainers " + String::valueOf(city->getSkillTrainerCount())); // Current Trainer Count:
	list->addMenuItem("@city/city:current_mt " + String::valueOf(city->getMissionTerminalCount())); // Current Terminal Count:

	for (int i = 0; i < cityTaxes.size(); ++i) {
		CityTax* cityTax = &cityTaxes.get(i);
		int tax = city->getTax(i);
		String prompt = cityTax->getStatusPrompt();

		if (prompt.isEmpty())
			continue;

		if (prompt == "@city/city:travel_cost_prompt" && !city->hasShuttleInstallation()) {
			prompt += "_none";
			list->addMenuItem(prompt);
		} else {
			list->addMenuItem(prompt + " " + String::valueOf(tax));
		}
	}

	creature->sendMessage(list->generateMessage());
}

void CityManagerImplementation::sendStructureReport(CityRegion* city, CreatureObject* creature, SceneObject* terminal) {
	PlayerObject* ghost = creature->getPlayerObject();

	if (ghost == NULL)
		return;

	ManagedReference<SuiListBox*> maintList = new SuiListBox(creature, SuiWindowType::CITY_TREASURY_REPORT);
	maintList->setPromptTitle("@city/city:structure_list_t"); // City Structure Report
	maintList->setPromptText("@city/city:structure_list_d");

	for (int i = 0; i < city->getStructuresCount(); i++) {
		ManagedReference<StructureObject*> structure = city->getCivicStructure(i);
		if (structure != NULL)
			maintList->addMenuItem(structure->getObjectName()->getFullPath() + " - Condition : "
					+ String::valueOf((1.0f * structure->getMaxCondition() - structure->getConditionDamage())
							/ structure->getMaxCondition() * 100) + "%", i);

	}

	for (int i = 0; i < city->getDecorationCount(); i++) {
		ManagedReference<StructureObject*> deco = cast<StructureObject*> (city->getCityDecoration(i));

		if (deco != NULL)
			maintList->addMenuItem(deco->getObjectName()->getFullPath() + " - Condition : "
					+ String::valueOf((1.0f * deco->getMaxCondition() - deco->getConditionDamage())
							/ deco->getMaxCondition() * 100) + "%", i);
	}

	ghost->addSuiBox(maintList);
	creature->sendMessage(maintList->generateMessage());
}

void CityManagerImplementation::promptWithdrawCityTreasury(CityRegion* city, CreatureObject* mayor, SceneObject* terminal) {
	if (!city->isMayor(mayor->getObjectID()))
		return;

	if (!mayor->checkCooldownRecovery("city_withdrawal") && !mayor->getPlayerObject()->isPrivileged()) {
		mayor->sendSystemMessage("@city/city:withdraw_daily"); //You may only withdraw from the city treasury once per day.
		return;
	}

	ManagedReference<CityTreasuryWithdrawalSession*> session = new CityTreasuryWithdrawalSession(mayor, city, terminal);
	mayor->addActiveSession(SessionFacadeType::CITYWITHDRAW, session);
	session->initializeSession();
}

void CityManagerImplementation::withdrawFromCityTreasury(CityRegion* city, CreatureObject* mayor, int value, const String& reason, SceneObject* terminal) {
	/**
	 string/en/city/city.stf	264	treasury_withdraw_from	City Treasury Authority
	 string/en/city/city.stf	265	treasury_withdraw_subject	City Treasury Withdrawal
	 string/en/city/city.stf	266	treasury_withdraw_body	Attention! Mayor %TO has made a withdrawal from the City Treasury. Amount: %DI Reason: %TT - Treasury Authority
	 */

	Locker locker(mayor);
	Locker clocker(city, mayor);

	int minWithdrawal = city->getMinWithdrawal();

	if (city->getCityTreasury() < minWithdrawal) {
		mayor->sendSystemMessage("@city/city:no_money"); //There isn't any money to transfer!
		return;
	}

	ManagedReference<CityTreasuryWithdrawalSession*> session = mayor->getActiveSession(SessionFacadeType::CITYWITHDRAW).castTo<CityTreasuryWithdrawalSession*>();

	if (session == NULL) {
		mayor->sendSystemMessage("@city/city:withdraw_treasury_error"); // Unable to complete withdraw. Please try again.
		return;
	}

	if (!mayor->checkCooldownRecovery("city_withdrawal") && !mayor->getPlayerObject()->isPrivileged()) {
		mayor->sendSystemMessage("@city/city:withdraw_daily"); //You may only withdraw from the city treasury once per day.
		session->cancelSession();
		return;
	}

	if (value < 0) {
		mayor->sendSystemMessage("@city/city:withdraw_treasury"); //You must select a positive amount to withdraw from the treasury.
		session->cancelSession();
		return;
	}

	int maxWithdrawal = MIN(city->getMaxWithdrawal(), city->getCityTreasury());

	if (value > maxWithdrawal || value < minWithdrawal) {
		mayor->sendSystemMessage("@city/city:withdraw_limits"); // You may only withdraw between 10,000 and 50,000 credits per day.
		session->cancelSession();
		return;
	}

	mayor->addBankCredits(value, true);
	city->subtractFromCityTreasury(value);

	mayor->addCooldown("city_withdrawal", CityManagerImplementation::treasuryWithdrawalCooldown);

	session->cancelSession();

	StringIdChatParameter message("@new_player:credits_reward"); // %DI credits have been deposited into your bank account.
	message.setDI(value);
	mayor->sendSystemMessage(message);

	StringIdChatParameter emailBody("@city/city:treasury_withdraw_body");
	emailBody.setDI(value);
	emailBody.setTO(mayor);
	emailBody.setTT(reason);

	sendMail(city, "@city/city:treasury_withdraw_from", "@city/city:treasury_withdraw_subject", emailBody, NULL);
}

void CityManagerImplementation::promptDepositCityTreasury(CityRegion* city, CreatureObject* creature, SceneObject* terminal) {
	PlayerObject* ghost = creature->getPlayerObject();

	if (ghost == NULL)
		return;

	ManagedReference<SuiTransferBox*> transfer = new SuiTransferBox(creature, SuiWindowType::CITY_TREASURY_DEPOSIT);
	transfer->setPromptTitle("@city/city:treasury_deposit"); //Treasury Deposit
	transfer->setPromptText("@city/city:treasury_deposit_d"); //Enter the amount you would like to transfer to the city treasury.
	transfer->addFrom("@city/city:funds", String::valueOf(creature->getCashCredits()), String::valueOf(creature->getCashCredits()), "1");
	transfer->addTo("@city/city:treasury", "0", "0", "1");
	transfer->setUsingObject(terminal);
	transfer->setForceCloseDistance(16.f);
	transfer->setCallback(new CityTreasuryDepositSuiCallback(zoneServer, city));

	ghost->addSuiBox(transfer);
	creature->sendMessage(transfer->generateMessage());
}

void CityManagerImplementation::depositToCityTreasury(CityRegion* city, CreatureObject* creature, int amount) {
	int cash = creature->getCashCredits();

	int total = cash - amount;

	if (total < 1 || total > cash) {
		creature->sendSystemMessage("@city/city:positive_deposit"); //You must select a positive amount to transfer to the treasury.
		return;
	}

	double currentTreasury = city->getCityTreasury();

	if ((int)currentTreasury + total > 100000000) {
		creature->sendSystemMessage("The maximum treasury a city can have is 100.000.000");
		return;
	}

	city->addToCityTreasury(total);
	creature->subtractCashCredits(total);

	StringIdChatParameter params("city/city", "deposit_treasury"); //You deposit %DI credits into the treasury.
	params.setDI(total);
	creature->sendSystemMessage(params);
}

void CityManagerImplementation::sendCitizenshipReport(CityRegion* city, CreatureObject* creature, SceneObject* terminal) {
	PlayerObject* ghost = creature->getPlayerObject();

	if (ghost == NULL)
		return;

	//Check if they already have the window open.
	if (ghost->hasSuiBoxWindowType(SuiWindowType::CITY_CITIZEN_REPORT))
		return;

	ManagedReference<SuiListBox*> listbox = new SuiListBox(creature, SuiWindowType::CITY_CITIZEN_REPORT);
	listbox->setPromptTitle("@city/city:citizen_list_t"); //City Citizenship Report
	listbox->setPromptText("@city/city:citizen_list_d"); //The following is a list of the currently declared citizens (residents) of this city. All citizens are elligible to vote.
	listbox->setUsingObject(terminal);
	listbox->setForceCloseDistance(16.f);
	listbox->addMenuItem("@city/city:reg_citizen_prompt"); // Registered Citizens:

	CitizenList* citizenList = city->getCitizenList();

	for (int i = 0; i < citizenList->size(); ++i) {
		ManagedReference<SceneObject*> citizen = zoneServer->getObject(citizenList->get(i));

		if (citizen != NULL) {
			String name = "@city/city:default \t" + citizen->getDisplayedName();

			if (city->isMilitiaMember(citizen->getObjectID()))
				name += " @city/city:militia_suffix"; // (Militia)

			listbox->addMenuItem(name);
		}
	}

	ghost->addSuiBox(listbox);
	creature->sendMessage(listbox->generateMessage());
}

void CityManagerImplementation::assessCitizens(CityRegion* city) {
	Locker locker(city);

	if (zoneServer->isServerLoading()) {
		city->scheduleCitizenAssessment(10);
		return;
	}

	info("Assessing city citizens: " + city->getRegionName(), true);

	city->cleanupCitizens();

	int citizens = city->getCitizenCount();

	ChatManager* chatManager = zoneServer->getChatManager();

	ManagedReference<SceneObject*> obj = zoneServer->getObject(city->getMayorID());
	CreatureObject* mayor = NULL;

	if (obj != NULL && obj->isPlayerCreature()) {
		mayor = cast<CreatureObject*> (obj.get());
	}

	if (citizens < citizensPerRank.get(0)) {
		if (mayor != NULL) {
			StringIdChatParameter params("city/city", "new_city_fail_body");
			UnicodeString subject = "@city/city:new_city_fail_subject"; // Zoning Permits Rejected

			chatManager->sendMail("@city/city:new_city_from", subject, params, mayor->getFirstName(), NULL);
		}

		ManagedReference<StructureObject*> cityhall = city->getCityHall();

		if (cityhall != NULL) {
			StructureManager::instance()->destroyStructure(cityhall);
		} else {
			locker.release();

			destroyCity(city);
		}

	} else {
		if (mayor != NULL) {
			StringIdChatParameter params("city/city", "new_city_success_body");
			UnicodeString subject = "@city/city:new_city_success_subject"; // New City Approved!

			chatManager->sendMail("@city/city:new_city_from", subject, params, mayor->getFirstName(), NULL);
		}

		city->setAssessmentPending(false);
	}
}

void CityManagerImplementation::processCityUpdate(CityRegion* city) {
	info("Processing city update: " + city->getRegionName(), true);

	ManagedReference<StructureObject*> ch = city->getCityHall();

	if (ch == NULL) {
		destroyCity(city);
		return;
	}

	int cityRank = 0;
	float radius = 0;

	try {
		cityRank = city->getCityRank();
{'fraction_non_alphanumeric': 0.150510650714911, 'fraction_numerical': 0.0056414745647310575, 'mean_word_length': 5.942066171505739, 'pattern_counts': {'":': 0, '<': 332, '<?xml version=': 0, '>': 529, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5452768', 'n_tokens_mistral': 17455, 'n_tokens_neox': 15996, 'n_words': 3017}