From 6eea20bf364fbdb52854de5bc6f513be0cc3c29c Mon Sep 17 00:00:00 2001 From: Light2 Date: Fri, 13 Jun 2014 03:40:41 +0200 Subject: [PATCH] More work on cities Fixed some bugs, more work on city management terminal --- .../terminal/city_maintenance_terminal.py | 347 ++++++++++++------ src/resources/common/AnimationType.java | 21 ++ src/resources/datatables/CivicStructures.java | 40 ++ .../objects/building/BuildingObject.java | 2 +- src/services/CharacterService.java | 7 +- src/services/housing/HouseTemplate.java | 9 + src/services/housing/HousingService.java | 1 + src/services/map/MapService.java | 9 + src/services/playercities/PlayerCity.java | 152 ++++---- .../playercities/PlayerCityService.java | 101 ++++- 10 files changed, 505 insertions(+), 184 deletions(-) create mode 100644 src/resources/datatables/CivicStructures.java diff --git a/scripts/radial/terminal/city_maintenance_terminal.py b/scripts/radial/terminal/city_maintenance_terminal.py index e5bab716..4aa8f402 100644 --- a/scripts/radial/terminal/city_maintenance_terminal.py +++ b/scripts/radial/terminal/city_maintenance_terminal.py @@ -1,64 +1,79 @@ from resources.common import RadialOptions +from resources.common import OutOfBand from protocol.swg import ResourceListForSurveyMessage from services.sui.SUIService import MessageBoxType from services.sui.SUIWindow import Trigger from java.util import Vector +from java.lang import System import main.NGECore import sys def createRadial(core, owner, target, radials): #(byte parentId, short optionId, byte optionType, String description) radials.clear() + city = core.playerCityService.getCityObjectIsIn(owner) + if not city: + return radials.add(RadialOptions(0, 7, 0, 'Examine')) - radials.add(RadialOptions(0, 216, 0, '@city/city:city_management')) - radials.add(RadialOptions(0, 226, 0, '@city/city:city_info')) - radials.add(RadialOptions(0, 227, 0, '@city/city:remove_trainers')) - radials.add(RadialOptions(2, 217, 0, '@city/city:city_name_new_t')) - radials.add(RadialOptions(2, 222, 0, '@city/city:city_register')) - radials.add(RadialOptions(2, 224, 0, '@city/city:unzone')) - radials.add(RadialOptions(2, 218, 0, '@city/city:city_militia')) - radials.add(RadialOptions(2, 219, 0, '@city/city:treasury_taxes')) - radials.add(RadialOptions(2, 221, 0, '@city/city:treasury_withdraw')) - radials.add(RadialOptions(2, 225, 0, '@city/city:city_specializations')) - radials.add(RadialOptions(2, 228, 0, '@city/city:align')) - radials.add(RadialOptions(3, 121, 0, '@city/city:non_citizen_city_status')) - radials.add(RadialOptions(3, 122, 0, '@city/city:rank_info_t')) - radials.add(RadialOptions(3, 123, 0, '@city/city:citizen_list_t')) - radials.add(RadialOptions(3, 230, 0, '@city/city:revoke_citizenship')) + radials.add(RadialOptions(0, 211, 0, '@city/city:city_info')) + if city.getMayorID() == owner.getObjectID(): + radials.add(RadialOptions(0, 216, 0, '@city/city:city_management')) - radials.add(RadialOptions(3, 231, 0, 'Add 10 citizens')) - radials.add(RadialOptions(3, 232, 0, 'Deduct 10 citizens')) + radials.add(RadialOptions(2, 212, 0, '@city/city:city_status')) + radials.add(RadialOptions(2, 213, 0, '@city/city:citizen_list_t')) + radials.add(RadialOptions(2, 214, 0, '@city/city:city_structures')) + radials.add(RadialOptions(2, 223, 0, '@city/city:rank_info_t')) + radials.add(RadialOptions(2, 224, 0, '@city/city:city_maint')) + radials.add(RadialOptions(2, 215, 0, '@city/city:treasury_status')) + radials.add(RadialOptions(2, 220, 0, '@city/city:treasury_deposit')) + + if owner.getPlayerObject().getCitizenship() != 3 and core.playerCityService.getPlayerCity(owner) == city: + radials.add(RadialOptions(2, 230, 0, '@city/city:revoke_citizenship')) + + if owner.getClient().isGM(): + radials.add(RadialOptions(2, 231, 0, 'Add 10 citizens')) + radials.add(RadialOptions(2, 232, 0, 'Deduct 10 citizens')) + + if city.getMayorID() != owner.getObjectID(): + print 'here' + return + + radials.add(RadialOptions(0, 216, 0, '@city/city:city_management')) + #radials.add(RadialOptions(0, 227, 0, '@city/city:remove_trainers')) not needed for pswg + radials.add(RadialOptions(3, 217, 0, '@city/city:city_name_new_t')) + if city.isRegistered(): + radials.add(RadialOptions(3, 222, 0, '@city/city:city_unregister')) + else: + radials.add(RadialOptions(3, 222, 0, '@city/city:city_register')) + if city.isZoningEnabled(): + radials.add(RadialOptions(3, 226, 0, '@city/city:unzone')) + else: + radials.add(RadialOptions(3, 226, 0, '@city/city:zone')) + + radials.add(RadialOptions(3, 218, 0, '@city/city:city_militia')) + radials.add(RadialOptions(3, 219, 0, '@city/city:treasury_taxes')) + radials.add(RadialOptions(3, 221, 0, '@city/city:treasury_withdraw')) + radials.add(RadialOptions(3, 225, 0, '@city/city:city_specializations')) + radials.add(RadialOptions(3, 228, 0, '@city/city:align')) return def handleSelection(core, owner, target, option): - playerCity = core.playerCityService.getPlayerCity(owner) - if playerCity==None: + playerCity = core.playerCityService.getCityObjectIsIn(owner) + if playerCity == None: return if option == 217: if owner is not None: handleSetCityName(core, owner, target, option) return - if option == 124: - if owner is not None: - core.housingService.createStatusSUIPage(owner,target) - return - if option == 129: - if owner is not None: - core.housingService.createPayMaintenanceSUIPage(owner,target) - return - if option == 50: - if owner is not None: - core.housingService.createRenameSUIPage(owner,target) - return if option == 222: if owner is not None: - handleRegisterMap(core, owner, target, option) - return - if option == 171: - if owner is not None: - core.housingService.handleListAllItems(owner,target) + if playerCity.getMayorID() == owner.getObjectID(): + if playerCity.isRegistered(): + handleUnregisterMap(core, owner, target, option) + else: + handleRegisterMap(core, owner, target, option) return if option == 219: if owner is not None: @@ -72,45 +87,134 @@ def handleSelection(core, owner, target, option): if owner is not None: handleWithdrawal(core, owner, target, option) return - if option == 121: + if option == 212: + if owner is not None: + handleCityInfo(core, owner, target, option) + return + if option == 223: if owner is not None: handleCityRankInfo(core, owner, target, option) return - if option == 122: - if owner is not None: - handleCityRankInfo(core, owner, target, option) - return - if option == 123: + if option == 213: if owner is not None: handleCitizenList(core, owner, target, option) return - if option == 231: if owner is not None: handle10Add(core, owner, target, option) return - if option == 232: if owner is not None: handle10Deduct(core, owner, target, option) return + if option == 220: + if owner is not None: + handleDeposit(core, owner, target, option) + return + if option == 226: + if owner is not None: + handleToggleZoning(core, owner, target, option) + return + +def handleDeposit(core, owner, target, option): + playerCity = core.playerCityService.getPlayerCity(owner) + suiSvc = core.suiService + suiWindow = suiSvc.createSUIWindow('Script.transfer', owner, target, 10) + suiWindow.setProperty('transaction.txtInputFrom:Text', '@city/city:total_funds') + suiWindow.setProperty('bg.caption.lblTitle:Text', '@city/city:treasury_deposit') + suiWindow.setProperty('Prompt.lblPrompt:Text', '@city/city:treasury_deposit_d') + suiWindow.setProperty('transaction.lblFrom:Text', '@city/city:total_funds') + suiWindow.setProperty('transaction.lblTo:Text', '@city/city:treasury') + suiWindow.setProperty('transaction.lblStartingFrom:Text', str(owner.getCashCredits())) + suiWindow.setProperty('transaction.lblStartingTo:Text', '') + suiWindow.setProperty('transaction.txtInputFrom:Text', str(owner.getCashCredits())) + suiWindow.setProperty('transaction.txtInputTo:Text', '') + suiWindow.setProperty('transaction:ConversionRatioFrom', '1') + suiWindow.setProperty('transaction:ConversionRatioTo', '1') + returnParams = Vector() + returnParams.add('transaction.txtInputFrom:Text') + returnParams.add('transaction.txtInputTo:Text') + suiWindow.addHandler(0, '', Trigger.TRIGGER_OK, returnParams, handleDepositSUI) + + suiSvc.openSUIWindow(suiWindow) + + return + +def handleDepositSUI(owner, window, eventType, returnList): + playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) + if not playerCity: + return + amount = int(returnList.get(0)) + cash = owner.getCashCredits() + deposit = cash - amount + if amount > cash or deposit < 1: + owner.sendSystemMessage('@city/city:positive_deposit', 0) + return + if playerCity.getCityTreasury() + deposit > 100000000: + owner.sendSystemMessage('@city/city:positive_deposit', 0) + return + playerCity.addToTreasury(deposit) + owner.deductCashCredits(deposit) + owner.sendSystemMessage(OutOfBand.ProsePackage('@city/city:deposit_treasury', deposit), 0) + return + +def handleToggleZoning(core, owner, target, option): + playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) + if not playerCity or playerCity.getMayorID() != owner.getObjectID(): + return + + playerCity.setZoningEnabled(!playerCity.isZoningEnabled()) + + if playerCity.isZoningEnabled(): + owner.sendSystemMessage('@city/city:zoning_enabled', 0) + else: + owner.sendSystemMessage('@city/city:zoning_disabled', 0) + + return + + def handle10Add(core, owner, target, option): playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) playerCity.Add10MoreCitizens() return - def handle10Deduct(core, owner, target, option): playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) playerCity.Deduct10Citizens() return -def setnameCallBack(owner, window, eventType, returnList): +def handleSetCityName(core, owner, target, option): + playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) + if not playerCity: + return + if playerCity.getCityNameChangeCooldown() > System.currentTimeMillis(): + owner.sendSystemMessage('You may only change the city name once in 4 weeks.', 0) + return + window = core.suiService.createInputBox(2, '@city/city:city_name_new_t','@city/city:city_name_new_d', owner, None, 0) + window.setProperty('txtInput:MaxLength', '40') + returnList = Vector() + returnList.add('txtInput:LocalText') + window.addHandler(0, '', Trigger.TRIGGER_OK, returnList, setNameCallBack) + + return + + +def setNameCallBack(owner, window, eventType, returnList): if returnList.size()==0: return + name = returnList.get(0) + if not core.characterService.checkName(name, owner.getClient(), True): + owner.sendSystemMessage('@player_structure:not_valid_name', 0) + return + if not core.playerCityService.doesCityNameExist(name): + owner.sendSystemMessage('@player_structure:cityname_not_unique', 0) + return + playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) - playerCity.setCityName(returnList.get(0)) + playerCity.setCityName(name) + playerCity.setCityNameChangeCooldown(System.currentTimeMillis() + str(604800 * 4 * 1000)) + # TODO send mail owner.sendSystemMessage('@city/city:name_changed', 0) return @@ -118,7 +222,7 @@ def setnameCallBack(owner, window, eventType, returnList): def handleAdjustTaxes(core, owner, target, option): playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) - if playerCity.getRank()<2: + if playerCity.getRank() < 2: owner.sendSystemMessage('@city/city:no_rank_taxes', 0) return @@ -284,54 +388,16 @@ def handleSetGarageTaxCallBack(owner, window, eventType, returnList): def handleWithdrawal(core, owner, target, option): playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) - window = core.suiService.createSUIWindow("Script.transfer", owner, target, 10) - - window.setProperty("bg.caption.lblTitle:Text", "@city/city:treasury_withdraw_subject") - window.setProperty("Prompt.lblPrompt:Text", "@city/city:treasury_withdraw_prompt" + "\n \n @city/city:treasury_status : %s" % playerCity.getCityTreasury()) - - window.setProperty("msgPayMaintenance", "transaction.txtInputFrom"); - - window.setProperty("transaction.lblFrom:Text", "@city/city:treasury_balance_t"); - window.setProperty("transaction.lblTo:Text", "@city/city:treasury_withdraw"); - window.setProperty("transaction.lblFrom", "@city/city:treasury_balance_t"); - window.setProperty("transaction.lblTo", "@city/city:treasury_withdraw"); - - window.setProperty("transaction.lblStartingFrom:Text", '%s' % playerCity.getCityTreasury()); - window.setProperty("transaction.lblStartingTo:Text", '0'); - - window.setProperty("transaction:InputFrom", "555555"); - window.setProperty("transaction:InputTo", "666666"); - - window.setProperty("transaction:txtInputFrom", '%s' % playerCity.getCityTreasury()); - window.setProperty("transaction:txtInputTo", "1"); - window.setProperty("transaction.txtInputFrom:Text", '%s' % playerCity.getCityTreasury()); - window.setProperty("transaction.txtInputTo:Text", "" + "0"); - - window.setProperty("transaction.ConversionRatioFrom", "1"); - window.setProperty("transaction.ConversionRatioTo", "0"); - - window.setProperty("btnOk:visible", "True"); - window.setProperty("btnCancel:visible", "True"); - window.setProperty("btnOk:Text", "@ok"); - window.setProperty("btnCancel:Text", "@cancel"); - - returnList = Vector() - returnList.add('transaction.txtInputFrom:Text') - returnList.add('ransaction.txtInputTo:Text') - window.addHandler(0, '', Trigger.TRIGGER_OK, returnList, setWithdrawalCallBack) - window.addHandler(1, '', Trigger.TRIGGER_CANCEL, returnList, setWithdrawalCallBack) - core.suiService.openSUIWindow(window); - return - -def setWithdrawalCallBack(owner, window, eventType, returnList): - if returnList.size()==0: + if not playerCity: return - playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) - playerCity.setCityName(returnList.get(0)) - owner.sendSystemMessage('@city/city:name_changed', 0) - return + if playerCity.getCityTreasuryWithdrawalCooldown() > System.currentTimeMillis(): + owner.sendSystemMessage('@city/city:withdraw_daily', 0) + return + core.playerCityService.handleCityTreasuryWithdrawal(owner) + -def handleCitizenList(core, owner, target, option): +def handleCitizenList(core, owner, target, option): + # wasnt there a new list layout in NGE ? window = core.suiService.createSUIWindow('Script.listBox', owner, target, 0); window.setProperty('bg.caption.lblTitle:Text', '@city/city:citizen_list_t') window.setProperty('Prompt.lblPrompt:Text', '@city/city:citizen_list_prompt') @@ -340,7 +406,12 @@ def handleCitizenList(core, owner, target, option): index = 1; for citizen in playerCity.getCitizens(): citizenObject = core.objectService.getObject(citizen) - window.addListBoxMenuItem(citizenObject.getCustomName(),index) + if not citizenObject: + continue + name = citizenObject.getCustomName() + if citizenObject.getPlayerObject().getCitizenship() == 2: + name += ' (Militia)' + window.addListBoxMenuItem(name,index) index += 1 window.setProperty('btnOk:visible', 'True') @@ -351,35 +422,43 @@ def handleCitizenList(core, owner, target, option): returnList = Vector() returnList.add('txtInput:LocalText') window.addHandler(0, '', Trigger.TRIGGER_OK, returnList, setCitizenListCallBack) - window.addHandler(1, '', Trigger.TRIGGER_CANCEL, returnList, setCitizenListCallBack) core.suiService.openSUIWindow(window); return def setCitizenListCallBack(owner, window, eventType, returnList): - #handle waypoint creation to citizen's house + idx = returnList.get(0) + # todo add waypoint return -def handleCityRankInfo(core, owner, target, option): - window = core.suiService.createSUIWindow('Script.listBox', owner, target, 0); - window.setProperty('bg.caption.lblTitle:Text', '@city/city:rank_info_t') - window.setProperty('Prompt.lblPrompt:Text', '@city/city:rank_info_d') +def handleCityInfo(core, owner, target, option): + window = core.suiService.createSUIWindow('Script.listBox', owner, None, 0); + window.setProperty('bg.caption.lblTitle:Text', '@city/city:city_info_t') + window.setProperty('Prompt.lblPrompt:Text', '@city/city:city_info_d') playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) - - window.addListBoxMenuItem('City : ' + playerCity.getCityName(),0) - window.addListBoxMenuItem('@city/city:radius_prompt : %s' % playerCity.getCityRadius(),1) - window.addListBoxMenuItem('@city/city:city_rank_prompt : @city/city:rank' + str(playerCity.getRank()),2) - window.addListBoxMenuItem('@city/city:reg_citizen_prompt : %s' % len(playerCity.getCitizens()),3) - if playerCity.getRank()>=3 and playerCity.getSpecialization()>-1: + mayor = core.objectService.getObject(playerCity.getMayorID()) + if not mayor: + mayor = core.objectService.getCreatureFromDB(playerCity.getMayorID()) + window.addListBoxMenuItem('@city/city:name_prompt : ' + playerCity.getCityName(),0) + if mayor: + window.addListBoxMenuItem('@city/city:mayor_prompt : ' + mayor.getCustomName(),5) + window.addListBoxMenuItem('@city/city:location_prompt : ' + str(playerCity.getCityCenterPosition().x) + ' ' + str(playerCity.getCityCenterPosition().z),6) + window.addListBoxMenuItem('@city/city:radius_prompt : %s' % playerCity.getCityRadius(),1) + window.addListBoxMenuItem('@city/city:reg_citizen_prompt : %s' % len(playerCity.getCitizens()),7) + window.addListBoxMenuItem('@kb/kb_player_cities_n:civic_structures_n : %s' % playerCity.getCivicStructuresCount(),8) + window.addListBoxMenuItem('@city/city:decorations : %s' % 0,9) # todo when decorations are implemented + if playerCity.getRank() >= 3 and playerCity.getSpecialization() > -1: window.addListBoxMenuItem('@city/city:specialization_prompt : @city/city:' + playerCity.getSpecializationSTFNamesAsList().get(playerCity.getSpecialization()),4) + + window.addListBoxMenuItem('@city/city:city_rank_prompt : @city/city:rank' + str(playerCity.getRank()),2) + # TODO add tax info window.setProperty('btnOk:visible', 'True') window.setProperty('btnCancel:visible', 'True') window.setProperty('btnOk:Text', '@ok') window.setProperty('btnCancel:Text', '@cancel') window.setProperty('btnCancel:Text', '@cancel') - playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) returnList = Vector() returnList.add('txtInput:LocalText') window.addHandler(0, '', Trigger.TRIGGER_OK, returnList, setCityInfoCallBack) @@ -391,6 +470,29 @@ def setCityInfoCallBack(owner, window, eventType, returnList): #handle waypoint creation to citizen's house return +def handleCityRankInfo(core, owner, target, option): + window = core.suiService.createSUIWindow('Script.listBox', owner, None, 0); + window.setProperty('bg.caption.lblTitle:Text', '@city/city:rank_info_t') + window.setProperty('Prompt.lblPrompt:Text', '@city/city:rank_info_d') + + playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) + rank = playerCity.getRank() + nextRank = rank + 1 + if nextRank > 5: + nextRank = 5 + window.addListBoxMenuItem('@city/city:city_rank_prompt : @city/city:rank' + str(playerCity.getRank()),1) + window.addListBoxMenuItem('@city/city:reg_citizen_prompt : %s' % len(playerCity.getCitizens()),2) + window.addListBoxMenuItem('@city/city:pop_req_current_rank : ' + str(playerCity.getReqCitizenCountForRank(rank)),3) + window.addListBoxMenuItem('@city/city:pop_req_next_rank : ' + str(playerCity.getReqCitizenCountForRank(nextRank)),4) + window.setProperty('btnOk:visible', 'True') + window.setProperty('btnCancel:visible', 'True') + window.setProperty('btnOk:Text', '@ok') + window.setProperty('btnCancel:Text', '@cancel') + window.setProperty('btnCancel:Text', '@cancel') + core.suiService.openSUIWindow(window); + + return + def handleRegisterMap(core, owner, target, option): playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) @@ -401,11 +503,31 @@ def handleRegisterMap(core, owner, target, option): if owner.getObjectId()!=playerCity.getMayorID(): owner.sendSystemMessage('@city/city:cant_register', 0) return + + if playerCity.isRegistered(): + return - # now register it somehow - + core.mapService.addLocation(owner.getPlanet(), playerCity.getCityName(), playerCity.getCityCenterPosition().x, playerCity.getCityCenterPosition().z, 17, 0, 0) return +def handleUnregisterMap(core, owner, target, option): + + playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) + if playerCity.getRank()<3: + owner.sendSystemMessage('@city/city:cant_register_rank', 0) + return + + if owner.getObjectId()!=playerCity.getMayorID(): + owner.sendSystemMessage('@city/city:cant_register', 0) + return + + if not playerCity.isRegistered(): + return + + core.mapService.removeLocation(owner.getPlanet(), playerCity.getCityCenterPosition().x, playerCity.getCityCenterPosition().z, 17) + return + + def setRegisterMapCallBack(owner, window, eventType, returnList): if returnList.size()==0: return @@ -417,7 +539,7 @@ def setRegisterMapCallBack(owner, window, eventType, returnList): def handleSpecialization(core, owner, target, option): playerCity = main.NGECore.getInstance().playerCityService.getPlayerCity(owner) - if playerCity.getRank()<3: + if playerCity.getRank() < 3: owner.sendSystemMessage('@city/city:no_rank_spec', 0) return @@ -434,9 +556,6 @@ def handleSpecialization(core, owner, target, option): window.addListBoxMenuItem('@city/city:city_spec_doctor',6) window.addListBoxMenuItem('@city/city:city_spec_research',7) window.addListBoxMenuItem('@city/city:city_spec_sample_rich',8) - window.addListBoxMenuItem('@city/city:city_spec_master_manufacturing',9) - window.addListBoxMenuItem('@city/city:city_spec_master_healing',10) - window.addListBoxMenuItem('@city/city:city_spec_stronghold',11) window.setProperty('btnOk:visible', 'True') window.setProperty('btnCancel:visible', 'True') diff --git a/src/resources/common/AnimationType.java b/src/resources/common/AnimationType.java index b0bd9a17..d426e009 100644 --- a/src/resources/common/AnimationType.java +++ b/src/resources/common/AnimationType.java @@ -1,3 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2013 + * + * This File is part of NGECore2. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Using NGEngine to work with NGECore2 is making a combined work based on NGEngine. + * Therefore all terms and conditions of the GNU Lesser General Public License cover the combination. + ******************************************************************************/ package resources.common; public class AnimationType { diff --git a/src/resources/datatables/CivicStructures.java b/src/resources/datatables/CivicStructures.java new file mode 100644 index 00000000..8de2f8c9 --- /dev/null +++ b/src/resources/datatables/CivicStructures.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2013 + * + * This File is part of NGECore2. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Using NGEngine to work with NGECore2 is making a combined work based on NGEngine. + * Therefore all terms and conditions of the GNU Lesser General Public License cover the combination. + ******************************************************************************/ +package resources.datatables; + +public class CivicStructures { + + public static final int BANK = 1; + public static final int CLONING_FACILITY = 2; + public static final int MEDICAL_CENTER = 3; + public static final int GARAGE = 4; + public static final int CANTINA = 5; + public static final int THEATER = 6; + public static final int SHUTTLEPORT = 7; + public static final int SMALL_GARDEN = 8; + public static final int MEDIUM_GARDEN = 9; + public static final int LARGE_GARDEN = 10; + + // [CiviCode - 1] for rank + public static final int[] rankRequired = new int[] { 2, 3, 3, 2, 2, 4, 4, 1, 2, 3}; + +} diff --git a/src/resources/objects/building/BuildingObject.java b/src/resources/objects/building/BuildingObject.java index 47637f49..9ee735d1 100644 --- a/src/resources/objects/building/BuildingObject.java +++ b/src/resources/objects/building/BuildingObject.java @@ -92,7 +92,7 @@ public class BuildingObject extends TangibleObject implements IPersistent, Seria public Baseline getOtherVariables() { Baseline baseline = super.getOtherVariables(); baseline.put("maintenanceAmount", (float) 0); - baseline.put("outstandingMaintenance", (float) 0); + baseline.put("outstandingMaintenance", 0); baseline.put("baseMaintenanceRate", 0); baseline.put("deedTemplate", ""); baseline.put("residency", false); diff --git a/src/services/CharacterService.java b/src/services/CharacterService.java index 5e44d250..948a49b8 100644 --- a/src/services/CharacterService.java +++ b/src/services/CharacterService.java @@ -75,6 +75,7 @@ public class CharacterService implements INetworkDispatch { private DatabaseConnection databaseConnection2; private engine.resources.common.NameGen nameGenerator; private static final String allowedCharsRegex = "['-]?[A-Za-z]('[a-zA-Z]|-[a-zA-Z]|[a-zA-Z])*['-]?$"; + private static final String allowedCharsRegexWithSpace = "['-]?[A-Za-z]('[a-zA-Z]|-[a-zA-Z]|[a-zA-Z]| )*['-]?$"; public CharacterService(NGECore core) { @@ -89,9 +90,13 @@ public class CharacterService implements INetworkDispatch { } public boolean checkName(String name, Client client) { + return checkName(name, client, false); + } + + public boolean checkName(String name, Client client, boolean allowSpaces) { // TODO: check for dev names, profane names, iconic names etc try { - if(checkForDuplicateName(name, client.getAccountId()) || !name.matches(allowedCharsRegex)) + if(checkForDuplicateName(name, client.getAccountId()) || (!allowSpaces && !name.matches(allowedCharsRegex)) || (allowSpaces && !name.matches(allowedCharsRegexWithSpace))) return false; } catch (SQLException e) { e.printStackTrace(); diff --git a/src/services/housing/HouseTemplate.java b/src/services/housing/HouseTemplate.java index b85ce784..b13b5a93 100644 --- a/src/services/housing/HouseTemplate.java +++ b/src/services/housing/HouseTemplate.java @@ -16,6 +16,7 @@ public class HouseTemplate { private Vector placeablePlanets; private Map buildingSigns; private boolean isCivicStructure; + private int civicStructureType; public HouseTemplate(String deedTemplate, String buildingTemplate, int lotCost) { this.deedTemplate = deedTemplate; @@ -86,5 +87,13 @@ public class HouseTemplate { public void setCivicStructure(boolean isCivicStructure) { this.isCivicStructure = isCivicStructure; } + + public int getCivicStructureType() { + return civicStructureType; + } + + public void setCivicStructureType(int civicStructureType) { + this.civicStructureType = civicStructureType; + } } diff --git a/src/services/housing/HousingService.java b/src/services/housing/HousingService.java index 55b75aa6..cb7305ad 100644 --- a/src/services/housing/HousingService.java +++ b/src/services/housing/HousingService.java @@ -151,6 +151,7 @@ public class HousingService implements INetworkDispatch { building.setAttachment("structureOwner", actor.getObjectID()); building.setAttachment("isCondemned", false); building.setAttachment("isCivicStructure", houseTemplate.isCivicStructure()); + building.setAttachment("civicStructureType", houseTemplate.getCivicStructureType()); building.setAttachment("outstandingMaint", 0); building.addPlayerToAdminList(null, actor.getObjectID(), playerFirstName); building.setDeedTemplate(deed.getTemplate()); diff --git a/src/services/map/MapService.java b/src/services/map/MapService.java index 007f4281..edbbc908 100644 --- a/src/services/map/MapService.java +++ b/src/services/map/MapService.java @@ -115,6 +115,15 @@ public class MapService implements INetworkDispatch { } + public void removeLocation(Planet planet, float x, float y, byte category) { + if(locationMap.get(planet) == null) + return; + MapLocation location = locationMap.get(planet).stream().filter(l -> l.getX() == x && l.getY() == y && l.getCategory() == category).findFirst().orElse(null); + if(location == null) + return; + locationMap.get(planet).remove(location); + } + public String getClosestCityName(SWGObject object) { if(object.getPlanet() == null) diff --git a/src/services/playercities/PlayerCity.java b/src/services/playercities/PlayerCity.java index 1ab1442b..357514e1 100644 --- a/src/services/playercities/PlayerCity.java +++ b/src/services/playercities/PlayerCity.java @@ -37,6 +37,7 @@ import resources.common.collidables.AbstractCollidable; import resources.common.collidables.CollidableCircle; import resources.common.collidables.AbstractCollidable.EnterEvent; import resources.common.collidables.AbstractCollidable.ExitEvent; +import resources.datatables.CivicStructures; import resources.datatables.Options; import resources.objects.building.BuildingObject; import resources.objects.creature.CreatureObject; @@ -86,13 +87,7 @@ public class PlayerCity implements Serializable { "city_spec_research", "city_spec_sample_rich"}; - public static final int BANK = 1; - public static final int CLONING_FACILITY = 2; - public static final int GARAGE = 3; - public static final int MEDIUM_GARDEN = 4; - public static final int LARGE_GARDEN = 5; - public static final int SHUTTLEPORT = 6; - + public static final int[] citizensPerRank = new int[] { 5, 10, 15, 30, 40 }; private String cityName = ""; @@ -100,7 +95,7 @@ public class PlayerCity implements Serializable { private long cityHallId = 0; private long cityID = -1; private Point3D cityCenterPosition = new Point3D(0,0,0); - private int cityRadius = 0; + private int cityRadius = 150; private int cityRank = 1; private int specialization = 0; private long mayorID = 0L; @@ -120,6 +115,8 @@ public class PlayerCity implements Serializable { private boolean registered = false; private boolean zoningEnabled = false; private transient CollidableCircle area; + private long cityNameChangeCooldown; + private long cityTreasuryWithdrawalCooldown; //public static final long cityUpdateSpan = 7*86400*1000; public static final long cityUpdateSpan = 100*1000; @@ -141,7 +138,6 @@ public class PlayerCity implements Serializable { public PlayerCity(CreatureObject founder, long cityId, BuildingObject cityHall) { setCityCenterPosition(cityHall.getPosition()); setCityHallId(cityHall.getObjectID()); - setCityRadius(150); setFoundationTime(System.currentTimeMillis()); setMaintenanceFee(1); setNextCityUpdate(foundationTime+cityUpdateSpan); @@ -156,6 +152,8 @@ public class PlayerCity implements Serializable { area = new CollidableCircle(getCityCenterPosition(), cityRadius, NGECore.getInstance().terrainService.getPlanetByID(planetId)); area.getEventBus().subscribe(this); NGECore.getInstance().simulationService.addCollidable(area, area.getCenter().x, area.getCenter().z); + if(isRegistered()) + NGECore.getInstance().mapService.addLocation(NGECore.getInstance().terrainService.getPlanetByID(planetId), getCityName(), area.getCenter().x, area.getCenter().z, (byte) 17, (byte) 0, (byte) 0); } public void handleGrantZoning() { @@ -170,6 +168,13 @@ public class PlayerCity implements Serializable { } + public int getCivicStructuresCount() { + return (int) placedStructures.stream(). + map(NGECore.getInstance().objectService::getObject). + filter(o -> o.getAttachment("civicStructureType") != null && (int) o.getAttachment("civicStructureType") > 0). + count(); + } + public void processElection() { // ToDo: handle everything long winnerID = mayorID; @@ -267,16 +272,20 @@ public class PlayerCity implements Serializable { return; int newRank = getRank() + 1; - setRank(newRank); - sendCityExpandMail(); - addNewStructures(); - fixupCitizens(); + boolean rankCapped = core.playerCityService.isRankCapped(core.terrainService.getPlanetByID(planetId), newRank); + sendCityExpandMail(rankCapped); + + if(!rankCapped) { + setRank(newRank); + addNewStructures(); + fixupCitizens(); + } } private void addNewStructures() { NGECore core = NGECore.getInstance(); - List objects = core.simulationService.get(core.terrainService.getPlanetByID(planetId), area.getCenter().x, area.getCenter().x, 500); + List objects = core.simulationService.get(core.terrainService.getPlanetByID(planetId), area.getCenter().x, area.getCenter().z, 500); objects.stream().filter(o -> o instanceof BuildingObject && o.getAttachment("structureOwner") != null && area.doesCollide(o)).map(SWGObject::getObjectID).forEach(this::addNewStructure); } @@ -372,31 +381,11 @@ public class PlayerCity implements Serializable { Vector wreckingBall = new Vector(); for (long structureID : placedStructures) { SWGObject structure = NGECore.getInstance().objectService.getObject(structureID); - int structureCode = (int) structure.getAttachment("CityStructureCode"); - - if (structureCode==BANK && cityRank<2){ + if(structure.getAttachment("civicStructureType") == null || (int) structure.getAttachment("civicStructureType") == 0) + return; + int structureCode = (int) structure.getAttachment("civicStructureType"); + if(getRank() < CivicStructures.rankRequired[structureCode - 1]) wreckingBall.add(structure); - } - - if (structureCode==CLONING_FACILITY && cityRank<3){ - wreckingBall.add(structure); - } - - if (structureCode==GARAGE && cityRank<2){ - wreckingBall.add(structure); - } - - if (structureCode==MEDIUM_GARDEN && cityRank<2){ - wreckingBall.add(structure); - } - - if (structureCode==LARGE_GARDEN && cityRank<3){ - wreckingBall.add(structure); - } - - if (structureCode==SHUTTLEPORT && cityRank<4){ - wreckingBall.add(structure); - } } for (SWGObject wreck : wreckingBall){ @@ -563,11 +552,11 @@ public class PlayerCity implements Serializable { this.mayorID = mayorID; } - public int getCityTreasury() { + public synchronized int getCityTreasury() { return cityTreasury; } - public void setCityTreasury(int cityTreasury) { + public synchronized void setCityTreasury(int cityTreasury) { this.cityTreasury = cityTreasury; } @@ -672,11 +661,11 @@ public class PlayerCity implements Serializable { this.cityBanList = cityBanList; } - public void addToTreasury(int amountToAdd) { + public synchronized void addToTreasury(int amountToAdd) { cityTreasury += amountToAdd; } - public void removeFromTreasury(int amountToDeduct) { + public synchronized void removeFromTreasury(int amountToDeduct) { cityTreasury -= amountToDeduct; } @@ -694,6 +683,7 @@ public class PlayerCity implements Serializable { public void setCityRadius(int cityRadius) { this.cityRadius = cityRadius; + area.setRadius(cityRadius); } public Vector getMilitiaList() { @@ -934,32 +924,18 @@ public class PlayerCity implements Serializable { actorMail.setRecieverId(citizen); actorMail.setStatus(Mail.NEW); actorMail.setTimeStamp((int) (new Date().getTime() / 1000)); - actorMail.setMessage("@city/city:lost_citizen_body"); actorMail.setSubject("@city/city:lost_citizen_subject"); actorMail.setSenderName("City " + this.cityName); - - List attachments = new ArrayList(); - WaypointObject constructionWaypoint = (WaypointObject)NGECore.getInstance().objectService.createObject("object/waypoint/shared_world_waypoint_blue.iff", citizenObject.getPlanet(), citizenObject.getPosition().x, 0 ,citizenObject.getPosition().z); - WaypointAttachment attachment = new WaypointAttachment(); - attachment.active = false; - attachment.cellID = constructionWaypoint.getCellId(); - attachment.color = (byte)1; - attachment.name = "City"; - attachment.planetCRC = engine.resources.common.CRC.StringtoCRC(citizenObject.getPlanet().getName()); - attachment.positionX = citizenObject.getPosition().x; - attachment.positionY = 0; - attachment.positionZ = citizenObject.getPosition().z; - attachments.add(attachment); - actorMail.setWaypointAttachments(attachments); - + actorMail.addProseAttachment(new ProsePackage("@city/city:lost_citizen_body", "TO", newCitizen.getCustomName())); + NGECore.getInstance().chatService.storePersistentMessage(actorMail); - if (newCitizen.getClient()!=null) - NGECore.getInstance().chatService.sendPersistentMessageHeader(newCitizen.getClient(), actorMail); + if (citizenObject.getClient()!=null) + NGECore.getInstance().chatService.sendPersistentMessageHeader(citizenObject.getClient(), actorMail); } } - public void sendCityExpandMail() { + public void sendCityExpandMail(boolean rankCapped) { NGECore core = NGECore.getInstance(); CreatureObject mayor = core.objectService.getObject(getMayorID()) == null ? core.objectService.getCreatureFromDB(getMayorID()) : (CreatureObject) core.objectService.getObject(getMayorID()); @@ -970,10 +946,14 @@ public class PlayerCity implements Serializable { mail.setRecieverId(getMayorID()); mail.setStatus(Mail.NEW); mail.setTimeStamp((int) (new Date().getTime() / 1000)); - mail.setSubject("@city/city:city_contract_subject"); mail.setSenderName("@city/city:new_city_from"); - mail.addProseAttachment(new ProsePackage("@city/city:city_expand_body", "TO", cityName, cityRank)); - + if(rankCapped) { + mail.setSubject("@city/city:city_expand_cap_subject"); + mail.addProseAttachment(new ProsePackage("@city/city:city_expand_cap_body", "TO", cityName, cityRank + 1)); + } else { + mail.setSubject("@city/city:city_expand_subject"); + mail.addProseAttachment(new ProsePackage("@city/city:city_expand_body", "TO", cityName, cityRank)); + } core.chatService.storePersistentMessage(mail); if(mayor.getClient() != null) core.chatService.sendPersistentMessageHeader(mayor.getClient(), mail); @@ -1211,6 +1191,50 @@ public class PlayerCity implements Serializable { public void setElectionList(Map electionList) { this.electionList = electionList; } + + public int getReqCitizenCountForRank(int rank) { + return citizensPerRank[rank - 1]; + } + + public long getCityNameChangeCooldown() { + return cityNameChangeCooldown; + } + + public void setCityNameChangeCooldown(long cityNameChangeCooldown) { + this.cityNameChangeCooldown = cityNameChangeCooldown; + } + + public long getCityTreasuryWithdrawalCooldown() { + return cityTreasuryWithdrawalCooldown; + } + + public void setCityTreasuryWithdrawalCooldown(long cityTreasuryWithdrawalCooldown) { + this.cityTreasuryWithdrawalCooldown = cityTreasuryWithdrawalCooldown; + } + + public void sendTreasuryWithdrawalMail(CreatureObject mayor, int amount, String reason) { + Vector citizenList = getCitizens(); + NGECore core = NGECore.getInstance(); + for (long citizen : citizenList){ + CreatureObject citizenObject = core.objectService.getObject(citizen) == null ? core.objectService.getCreatureFromDB(citizen) : (CreatureObject) core.objectService.getObject(citizen); + if(citizenObject == null) + continue; + Mail actorMail = new Mail(); + actorMail.setMailId(NGECore.getInstance().chatService.generateMailId()); + actorMail.setRecieverId(citizen); + actorMail.setStatus(Mail.NEW); + actorMail.setTimeStamp((int) (new Date().getTime() / 1000)); + actorMail.setSubject("@city/city:treasury_withdraw_subject"); + actorMail.setSenderName("@city/city:treasury_withdraw_from"); + actorMail.addProseAttachment(new ProsePackage("@city/city:treasury_withdraw_body", "TO", mayor.getCustomName(), "TT", reason, amount)); + + NGECore.getInstance().chatService.storePersistentMessage(actorMail); + if (citizenObject.getClient() != null) + NGECore.getInstance().chatService.sendPersistentMessageHeader(citizenObject.getClient(), actorMail); + + } + + } } diff --git a/src/services/playercities/PlayerCityService.java b/src/services/playercities/PlayerCityService.java index 75f2a1b8..cc99e1da 100644 --- a/src/services/playercities/PlayerCityService.java +++ b/src/services/playercities/PlayerCityService.java @@ -38,6 +38,7 @@ import resources.objects.building.BuildingObject; import resources.objects.creature.CreatureObject; import resources.objects.tangible.TangibleObject; import services.chat.Mail; +import services.sui.SUIService.InputBoxType; import services.sui.SUIWindow; import services.sui.SUIWindow.SUICallback; import services.sui.SUIWindow.Trigger; @@ -135,7 +136,8 @@ public class PlayerCityService implements INetworkDispatch { return; } - final SUIWindow window = core.suiService.createInputBox(2, "@city/city:city_name_t","@city/city:city_name_d", actor, null, 0); + final SUIWindow window = core.suiService.createInputBox(2, "@city/city:city_name_t","@city/city:city_name_d", actor, null, 0); + window.setProperty("txtInput:MaxLength", "40"); Vector returnList = new Vector(); returnList.add("txtInput:LocalText"); window.addHandler(0, "", Trigger.TRIGGER_OK, returnList, new SUICallback() { @@ -144,7 +146,7 @@ public class PlayerCityService implements INetworkDispatch { if (returnList.size() == 0 || returnList.get(0).length() == 0) return; String name = returnList.get(0); - if(!core.characterService.checkName(name, owner.getClient())) { + if(!core.characterService.checkName(name, owner.getClient(), true)) { actor.sendSystemMessage("@player_structure:not_valid_name", (byte) 0); return; } @@ -397,13 +399,104 @@ public class PlayerCityService implements INetworkDispatch { } core.simulationService.removeCollidable(city.getArea(), city.getArea().getCenter().x, city.getArea().getCenter().z); playerCities.remove(city); + if(city.isRegistered()) + core.mapService.removeLocation(core.terrainService.getPlanetByID(city.getPlanetId()), city.getCityCenterPosition().x, city.getCityCenterPosition().z, (byte) 17); core.objectService.deletePersistentObject(city.getCityID(), core.getCityODB()); } public void schedulePlayerCityUpdate(PlayerCity playerCity, long time) { scheduler.schedule(() -> { - playerCity.processCityUpdate(); + try { + playerCity.processCityUpdate(); + } catch(Exception e) { + e.printStackTrace(); + } }, time, TimeUnit.MILLISECONDS); - } + } + + public void handleCityTreasuryWithdrawal(CreatureObject owner) { + SUIWindow window = core.suiService.createInputBox(InputBoxType.INPUT_BOX_OK_CANCEL, "@city/city:withdraw_reason_t", "@city/city:withdraw_reason_d", owner, null, 0, (actor, eventType, returnList) -> { + String reason = returnList.get(0); + if(reason.length() <= 0) + return; + promptCityTreasuryWithdrawal(owner, reason); + }); + window.setProperty("txtInput:MaxLength", "255"); + core.suiService.openSUIWindow(window); + } + + public void promptCityTreasuryWithdrawal(CreatureObject owner, String reason) { + PlayerCity playerCity = core.playerCityService.getPlayerCity(owner); + SUIWindow window = core.suiService.createSUIWindow("Script.transfer", owner, null, 0); + + window.setProperty("bg.caption.lblTitle:Text", "@city/city:treasury_withdraw_subject"); + window.setProperty("Prompt.lblPrompt:Text", "@city/city:treasury_withdraw_prompt"); + + window.setProperty("msgPayMaintenance", "transaction.txtInputFrom"); + + window.setProperty("transaction.lblFrom:Text", "@city/city:treasury_balance_t"); + window.setProperty("transaction.lblTo:Text", "@city/city:treasury_withdraw"); + window.setProperty("transaction.lblFrom", "@city/city:treasury_balance_t"); + window.setProperty("transaction.lblTo", "@city/city:treasury_withdraw"); + + window.setProperty("transaction.lblStartingFrom:Text", String.valueOf(playerCity.getCityTreasury())); + window.setProperty("transaction.lblStartingTo:Text", "0"); + + window.setProperty("transaction:InputFrom", "555555"); + window.setProperty("transaction:InputTo", "666666"); + + window.setProperty("transaction:txtInputFrom", String.valueOf(playerCity.getCityTreasury())); + window.setProperty("transaction:txtInputTo", "1"); + window.setProperty("transaction.txtInputFrom:Text", String.valueOf(playerCity.getCityTreasury())); + window.setProperty("transaction.txtInputTo:Text", "0"); + + window.setProperty("transaction.ConversionRatioFrom", "1"); + window.setProperty("transaction.ConversionRatioTo", "0"); + + window.setProperty("btnOk:visible", "True"); + window.setProperty("btnCancel:visible", "True"); + window.setProperty("btnOk:Text", "@ok"); + window.setProperty("btnCancel:Text", "@cancel"); + + VectorreturnList = new Vector(); + returnList.add("transaction.txtInputFrom:Text"); + returnList.add("transaction.txtInputTo:Text"); + window.addHandler(0, "", Trigger.TRIGGER_OK, returnList, (actor, eventType, inputList) -> { + + if(inputList.size() < 2) + return; + + if(playerCity.getCityTreasury() <= 0) { + owner.sendSystemMessage("@city/city:no_money", (byte) 0); + return; + } + + if(playerCity.getCityTreasuryWithdrawalCooldown() > System.currentTimeMillis()) { + owner.sendSystemMessage("@city/city:withdraw_daily", (byte) 0); + return; + } + + int amount = Integer.parseInt(inputList.get(1)); + + if(amount < 5000 || amount > 150000) { + owner.sendSystemMessage("@city/city:withdraw_limits", (byte) 0); + return; + } + + if(amount > playerCity.getCityTreasury()) { + owner.sendSystemMessage("@city/city:withdraw_treasury_error", (byte) 0); + return; + } + + owner.addBankCredits(amount); + playerCity.removeFromTreasury(amount); + playerCity.setCityTreasuryWithdrawalCooldown(System.currentTimeMillis() + 86400000); // 24 hours + owner.sendSystemMessage(OutOfBand.ProsePackage("@city/city:you_withdraw_from_treasury", amount), (byte) 0); + playerCity.sendTreasuryWithdrawalMail(owner, amount, reason); + + }); + core.suiService.openSUIWindow(window); + + } }