diff --git a/scripts/houses/player_cityhall_corellia_style_01.py b/scripts/houses/player_cityhall_corellia_style_01.py index ff396cb0..1172942a 100644 --- a/scripts/houses/player_cityhall_corellia_style_01.py +++ b/scripts/houses/player_cityhall_corellia_style_01.py @@ -9,7 +9,7 @@ def setup(housingTemplates): houseTemplate.addPlaceablePlanet("corellia") houseTemplate.addPlaceablePlanet("talus") houseTemplate.setDefaultItemLimit(400) - houseTemplate.setBaseMaintenanceRate(308) + houseTemplate.setBaseMaintenanceRate(1000) housingTemplates.put(houseTemplate.getDeedTemplate(), houseTemplate) diff --git a/scripts/houses/player_cityhall_naboo_style_01.py b/scripts/houses/player_cityhall_naboo_style_01.py index 8bb52c69..b50059b8 100644 --- a/scripts/houses/player_cityhall_naboo_style_01.py +++ b/scripts/houses/player_cityhall_naboo_style_01.py @@ -10,7 +10,7 @@ def setup(housingTemplates): houseTemplate.addPlaceablePlanet("rori") houseTemplate.addPlaceablePlanet("dantooine") houseTemplate.setDefaultItemLimit(400) - houseTemplate.setBaseMaintenanceRate(308) + houseTemplate.setBaseMaintenanceRate(1000) housingTemplates.put(houseTemplate.getDeedTemplate(), houseTemplate) return \ No newline at end of file diff --git a/scripts/houses/player_cityhall_tatooine_style_01.py b/scripts/houses/player_cityhall_tatooine_style_01.py index 766ed7cf..7428068e 100644 --- a/scripts/houses/player_cityhall_tatooine_style_01.py +++ b/scripts/houses/player_cityhall_tatooine_style_01.py @@ -10,7 +10,7 @@ def setup(housingTemplates): houseTemplate.addPlaceablePlanet("dantooine") houseTemplate.addPlaceablePlanet("lok") houseTemplate.setDefaultItemLimit(400) - houseTemplate.setBaseMaintenanceRate(308) + houseTemplate.setBaseMaintenanceRate(1000) housingTemplates.put(houseTemplate.getDeedTemplate(), houseTemplate) return \ No newline at end of file diff --git a/scripts/radial/structure/structure_management_terminal.py b/scripts/radial/structure/structure_management_terminal.py index efad7fef..27c00f48 100644 --- a/scripts/radial/structure/structure_management_terminal.py +++ b/scripts/radial/structure/structure_management_terminal.py @@ -58,7 +58,7 @@ def handleSelection(core, owner, target, option): return if option == 127: if owner is not None: - core.housingService.declareResidency(owner,target) + core.housingService.declareResidency(owner,target.getGrandparent()) return if option == 171: if owner is not None: diff --git a/src/resources/objects/building/BuildingObject.java b/src/resources/objects/building/BuildingObject.java index ce8bc83e..141c5397 100644 --- a/src/resources/objects/building/BuildingObject.java +++ b/src/resources/objects/building/BuildingObject.java @@ -64,7 +64,7 @@ public class BuildingObject extends TangibleObject implements IPersistent, Seria super(objectID, planet, position, orientation, Template); getBaseline(3).set("volume", 255); setOptionsBitmask(Options.INVULNERABLE); - setConditionDamage(100); + setConditionDamage(0); setMaximumCondition(4320); setStaticObject(true); } @@ -73,7 +73,7 @@ public class BuildingObject extends TangibleObject implements IPersistent, Seria super(); getBaseline(3).set("volume", 255); setOptionsBitmask(Options.INVULNERABLE); - setConditionDamage(100); + setConditionDamage(0); setMaximumCondition(4320); setStaticObject(true); } @@ -84,7 +84,7 @@ public class BuildingObject extends TangibleObject implements IPersistent, Seria defendersList = new Vector(); getBaseline(3).set("volume", 255); setOptionsBitmask(Options.INVULNERABLE); - setConditionDamage(100); + setConditionDamage(0); setMaximumCondition(4320); } @@ -92,6 +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("baseMaintenanceRate", 0); baseline.put("deedTemplate", ""); baseline.put("residency", false); @@ -192,12 +193,21 @@ public class BuildingObject extends TangibleObject implements IPersistent, Seria otherVariables.set("maintenanceAmount", maintenanceAmount); } + public int getOutstandingMaintenance() { + return (int) otherVariables.get("outstandingMaintenance"); + } + + public void setOutstandingMaintenance(int maintenanceAmount) { + otherVariables.set("outstandingMaintenance", maintenanceAmount); + } + public int getBMR() { return (int) otherVariables.get("baseMaintenanceRate"); } public void setBMR(int BMR) { otherVariables.set("baseMaintenanceRate", BMR); + setMaximumCondition(BMR); } public String getDeedTemplate(){ diff --git a/src/services/housing/HousingService.java b/src/services/housing/HousingService.java index 22927ef6..27b70145 100644 --- a/src/services/housing/HousingService.java +++ b/src/services/housing/HousingService.java @@ -158,15 +158,17 @@ public class HousingService implements INetworkDispatch { building.setConditionDamage(100); // Ouch if (structureTemplate.contains("cityhall")){ - PlayerCity newCity = core.playerCityService.addNewPlayerCity(actor); + PlayerCity newCity = core.playerCityService.addNewPlayerCity(actor, building); building.setAttachment("structureCity", newCity.getCityID()); + building.setAttachment("isCivicStructure", true); actor.setAttachment("residentCity", newCity.getCityID()); + declareResidency(actor, building); } // Check for city founders joining a new city PlayerCity cityActorIsIn = core.playerCityService.getCityObjectIsIn(actor); - if (cityActorIsIn!=null) { + if (cityActorIsIn != null) { actor.setAttachment("Has24HZoningFor",cityActorIsIn.getCityID()); // for testing int cityActorHasZoning = (int)actor.getAttachment("Has24HZoningFor"); @@ -186,6 +188,9 @@ public class HousingService implements INetworkDispatch { public void startMaintenanceTask(BuildingObject building) { + if(building.getAttachment("isCivicStructure") != null && (boolean) building.getAttachment("isCivicStructure")) + return; + AtomicReference> ref = new AtomicReference>(); ref.set(scheduler.scheduleAtFixedRate(() -> { @@ -213,7 +218,7 @@ public class HousingService implements INetworkDispatch { building.setAttachment("isCondemned", true); building.setBuildingName(building.getBuildingName() + " \\#FF0000(CONDEMNED)\\#FFFFFF"); } - building.setAttachment("outstandingMaint", (int) building.getAttachment("outstandingMaint") + amount); + building.setOutstandingMaintenance(building.getOutstandingMaintenance() + amount); // TODO: lock down building, add option to pay outstanding maintenance and uncondemn building } } @@ -327,10 +332,9 @@ public class HousingService implements INetworkDispatch { public void process(SWGObject owner, int eventType, Vector returnList) { CreatureObject houseOwner = (CreatureObject)owner; core.suiService.closeSUIWindow(owner, 0); - if (returnList.get(0).equals(""+confirmCode)){ + if (returnList.get(0).equals(""+confirmCode)) { // handle creation of correct deed in player inventory PlayerObject player = (PlayerObject) houseOwner.getSlottedObject("ghost"); - PlayerCity city = core.playerCityService.getCityObjectIsIn(building); HouseTemplate houseTemplate = housingTemplates.get(houseToDeed.get(building.getTemplate())); TangibleObject deed = (TangibleObject) core.objectService.createObject(houseTemplate.getDeedTemplate(), owner.getPlanet()); @@ -349,11 +353,8 @@ public class HousingService implements INetworkDispatch { int costs = 800; houseOwner.setBankCredits(houseOwner.getBankCredits()-costs); - if(building.getAttachment("sign") != null) - core.objectService.destroyObject((SWGObject) building.getAttachment("sign")); - - core.objectService.destroyObject(building.getObjectID()); - + destroyStructure(building); + SWGObject ownerInventory = owner.getSlottedObject("inventory"); ownerInventory.add(deed); @@ -361,21 +362,7 @@ public class HousingService implements INetworkDispatch { if (player.getLotsRemaining() + houseTemplate.getLotCost() <= 10) { player.setLotsRemaining(player.getLotsRemaining() + houseTemplate.getLotCost()); } - - if(building.getResidency()) { - owner.setAttachment("residentBuilding", null); - } - - if(city != null) { - city.removeStructure(building.getObjectID()); - if(building.getResidency()) { - city.removeCitizen(owner.getObjectID()); - player.setHome(""); - player.setCitizenship(Citizenship.Homeless); - owner.setAttachment("residentCity", null); - } - } - + houseOwner.sendSystemMessage("@player_structure:processing_destruction",(byte)1); houseOwner.sendSystemMessage("@player_structure:deed_reclaimed",(byte)1); @@ -395,6 +382,38 @@ public class HousingService implements INetworkDispatch { core.suiService.openSUIWindow(window); } + public void destroyStructure(BuildingObject building) { + + PlayerCity city = core.playerCityService.getCityObjectIsIn(building); + CreatureObject owner = null; + if(building.getAttachment("structureOwner") != null) + owner = (CreatureObject) core.objectService.getObject((long) building.getAttachment("structureOwner")); + + if(building.getAttachment("sign") != null) + core.objectService.destroyObject((SWGObject) building.getAttachment("sign")); + + core.objectService.destroyObject(building.getObjectID()); + core.objectService.deletePersistentObject(building.getObjectID(), core.getSWGObjectODB()); + + if(building.getResidency()) + owner.setAttachment("residentBuilding", null); + + if(city != null) { + city.removeStructure(building.getObjectID()); + if(building.getResidency()) { + city.removeCitizen(owner.getObjectID()); + owner.getPlayerObject().setHome(""); + owner.getPlayerObject().setCitizenship(Citizenship.Homeless); + owner.setAttachment("residentCity", null); + } + if(building.getTemplate().contains("cityhall")) { + // destroy city + core.playerCityService.destroyCity(city); + } + } + + } + public void createPayMaintenanceSUIPage(SWGObject owner, TangibleObject target) { CreatureObject creature = (CreatureObject) owner; final BuildingObject building = (BuildingObject) target.getGrandparent(); @@ -525,8 +544,7 @@ public class HousingService implements INetworkDispatch { core.suiService.openSUIWindow(window); } - public void declareResidency(SWGObject owner, TangibleObject target) { - final BuildingObject building = (BuildingObject) target.getGrandparent(); + public void declareResidency(SWGObject owner, BuildingObject building) { PlayerCity cityActorIsIn = core.playerCityService.getCityObjectIsIn(building); if(owner.getObjectID() != (long) building.getAttachment("structureOwner")) { ((CreatureObject) owner).sendSystemMessage("@player_structure:declare_must_be_owner", (byte) 0); @@ -541,7 +559,7 @@ public class HousingService implements INetworkDispatch { return; } } - if(System.currentTimeMillis() < (long) owner.getAttachment("residencyCooldown")) { + if(System.currentTimeMillis() < (long) owner.getAttachment("residencyCooldown") && cityActorIsIn.getMayorID() != owner.getObjectID()) { ((CreatureObject) owner).sendSystemMessage(OutOfBand.ProsePackage("@player_structure:change_residence_time", "DI", (int) ((long) owner.getAttachment("residencyCooldown") - System.currentTimeMillis()) / 3600000), (byte) 0); return; } @@ -569,7 +587,10 @@ public class HousingService implements INetworkDispatch { cityActorIsIn.addCitizen(owner.getObjectID()); owner.setAttachment("residentCity", cityActorIsIn.getCityID()); ((CreatureObject) owner).getPlayerObject().setHome(cityActorIsIn.getCityName()); - ((CreatureObject) owner).getPlayerObject().setCitizenship(Citizenship.Citizen); + if(cityActorIsIn.getMayorID() == owner.getObjectID()) + ((CreatureObject) owner).getPlayerObject().setCitizenship(Citizenship.Mayor); + else + ((CreatureObject) owner).getPlayerObject().setCitizenship(Citizenship.Citizen); } } diff --git a/src/services/playercities/PlayerCity.java b/src/services/playercities/PlayerCity.java index 6f609e38..12b134be 100644 --- a/src/services/playercities/PlayerCity.java +++ b/src/services/playercities/PlayerCity.java @@ -28,12 +28,24 @@ import java.util.Date; import java.util.List; import java.util.Vector; +import net.engio.mbassy.listener.Handler; +import resources.common.OutOfBand; +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.Options; import resources.objects.building.BuildingObject; import resources.objects.creature.CreatureObject; +import resources.objects.tangible.TangibleObject; import resources.objects.waypoint.WaypointObject; import services.chat.Mail; import services.chat.WaypointAttachment; import main.NGECore; +import engine.clientdata.ClientFileManager; +import engine.clientdata.StfTable; +import engine.clientdata.visitors.DatatableVisitor; +import engine.resources.common.CRC; import engine.resources.objects.SWGObject; import engine.resources.scene.Point3D; @@ -88,7 +100,8 @@ public class PlayerCity implements Serializable { private String cityName = ""; - private int cityID = -1; + private long cityHallId = 0; + private long cityID = -1; private Point3D cityCenterPosition = new Point3D(0,0,0); private int cityRadius = 0; private int cityRank = 0; @@ -109,7 +122,8 @@ public class PlayerCity implements Serializable { private boolean shuttlePort = false; private boolean registered = false; private boolean zoningEnabled = false; - + private transient CollidableCircle area; + //private final long cityUpdateSpan = 7*86400*1000; private final long cityUpdateSpan = 100*1000; @@ -124,19 +138,23 @@ public class PlayerCity implements Serializable { private Vector militiaList = new Vector(); private Vector foundersList = new Vector(); - public PlayerCity(){} + public PlayerCity() {} - public PlayerCity(CreatureObject founder, int cityID){ - - setCityCenterPosition(founder.getPosition()); + public PlayerCity(CreatureObject founder, int cityID, BuildingObject cityHall) { + setCityCenterPosition(cityHall.getPosition()); + setCityHallId(cityHall.getObjectID()); setCityRadius(150); setFoundationTime(System.currentTimeMillis()); setMaintenanceFee(1); setNextCityUpdate(foundationTime+cityUpdateSpan); setNextElectionDate(foundationTime+legislationPeriod); - citizens.add(founder.getObjectID()); this.cityID = cityID; setMayorID(founder.getObjectID()); + init(); + } + + public void init() { + area.getEventBus().subscribe(this); } public void handleGrantZoning() { @@ -212,7 +230,7 @@ public class PlayerCity implements Serializable { setRank(++currentRank); // Expand to Metropolis sendCityExpandMailAll(); } - if (censusResult<15){ + if (censusResult<30){ setRank(--currentRank); // Contract to Township sendCityContractMailAll(); } @@ -245,9 +263,11 @@ public class PlayerCity implements Serializable { } citizenObject.setBankCredits(citizenObject.getBankCredits()-cumulatedPropertyTax); + if(core.objectService.getObject(citizen) == null) + core.objectService.persistObject(citizen, citizenObject, core.getSWGObjectODB()); cityTreasury += cumulatedPropertyTax; } - + calculateAndPayMaintenance(); demolishHighRankStructures(); setNextCityUpdate(System.currentTimeMillis()+cityUpdateSpan); } @@ -312,14 +332,56 @@ public class PlayerCity implements Serializable { } if (founderCitizenCount==founders.size()){ return true; // All founders are citizens now - } return false; } - public void calculateMaintenance() { + public void calculateAndPayMaintenance() { + NGECore core = NGECore.getInstance(); + // 1. Pay City Hall Maintenance + BuildingObject cityHall = (BuildingObject) core.objectService.getObject(cityHallId); + int cityHallMaintenance = 1000 + getRank() * 2500; + deductCivicStructureMaintenance(cityHall, cityHallMaintenance); + for(long placedStructures : new Vector(placedStructures)) { + BuildingObject building = (BuildingObject) core.objectService.getObject(placedStructures); + if(building != null) + deductCivicStructureMaintenance(building, building.getBMR()); + } + + } + + private void deductCivicStructureMaintenance(BuildingObject civicStructure, int amount) { + int maintOwed = civicStructure.getOutstandingMaintenance(); + if(amount > cityTreasury) { + int decay = civicStructure.getConditionDamage(); + int amountToPay = amount - cityTreasury; + int nextDecay = (int) (amountToPay / amount * civicStructure.getMaximumCondition() * 0.25f); + civicStructure.setOutstandingMaintenance(maintOwed - amountToPay); + civicStructure.setConditionDamage(decay + nextDecay); + cityTreasury = 0; + } else { + cityTreasury -= amount; + if(maintOwed > 0) { + if(maintOwed > cityTreasury) { + int decay = civicStructure.getConditionDamage(); + if(decay > 0) { + civicStructure.setConditionDamage(cityTreasury / (maintOwed / decay)); + civicStructure.setOutstandingMaintenance(maintOwed - cityTreasury); + cityTreasury = 0; + } + } else { + civicStructure.setOutstandingMaintenance(0); + cityTreasury -= maintOwed; + civicStructure.setConditionDamage(0); + } + } + } + if(civicStructure.getConditionDamage() >= civicStructure.getMaximumCondition()) { + // Destroy + } + } public void handleBanRequest() { @@ -536,11 +598,11 @@ public class PlayerCity implements Serializable { return; } - public int getCityID() { + public long getCityID() { return cityID; } - public void setCityID(int cityID) { + public void setCityID(long cityID) { this.cityID = cityID; } @@ -878,5 +940,89 @@ public class PlayerCity implements Serializable { public void removeStructure(long objectID) { placedStructures.remove(objectID); } + + public CollidableCircle getArea() { + return area; + } + + public void setArea(CollidableCircle area) { + this.area = area; + } + + public long getCityHallId() { + return cityHallId; + } + + public void setCityHallId(long cityHallId) { + this.cityHallId = cityHallId; + } + + public String getSpecialisationStfValue() { + try { + StfTable stf = new StfTable("clientdata/string/en/city/city.stf"); + + for (int s = 1; s < stf.getRowCount(); s++) { + String stfKey = stf.getStringById(s).getKey(); + + if (stfKey != null && stfKey != "" && stfKey.equals(specialisationSTFNames[specialization])) { + return stf.getStringById(s).getValue(); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + public String getCityRankStfValue() { + try { + StfTable stf = new StfTable("clientdata/string/en/city/city.stf"); + + for (int s = 1; s < stf.getRowCount(); s++) { + String stfKey = stf.getStringById(s).getKey(); + + if (stfKey != null && stfKey != "" && stfKey.equals("rank" + String.valueOf(cityRank))) { + return stf.getStringById(s).getValue(); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + + @Handler + public void onEnter(EnterEvent event) { + SWGObject object = event.object; + + if(object == null || !(object instanceof CreatureObject)) + return; + + String rank = getCityRankStfValue(); + String specialisation = getSpecialisationStfValue(); + if(specialisation.length() == 0) + ((CreatureObject) object).sendSystemMessage(OutOfBand.ProsePackage("@city/city:city_enter_city", "TT", getCityName(), "TO", rank), (byte) 0); + else + ((CreatureObject) object).sendSystemMessage(OutOfBand.ProsePackage("@city/city:city_enter_city", "TT", getCityName(), "TO", rank + ", " + specialisation), (byte) 0); + + // TODO: apply specialisation skill mods + } + + @Handler + public void onExit(ExitEvent event) { + SWGObject object = event.object; + + if(object == null || !(object instanceof CreatureObject)) + return; + + ((CreatureObject) object).sendSystemMessage(OutOfBand.ProsePackage("@city/city:city_leave_city", "TO", getCityName()), (byte) 0); + + // TODO: remove specialisation skill mods + + } + } diff --git a/src/services/playercities/PlayerCityService.java b/src/services/playercities/PlayerCityService.java index c3ea7fc2..cb4054fc 100644 --- a/src/services/playercities/PlayerCityService.java +++ b/src/services/playercities/PlayerCityService.java @@ -22,15 +22,19 @@ package services.playercities; import java.util.Collections; +import java.util.Date; import java.util.Map; +import java.util.Random; import java.util.Vector; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import resources.common.ProsePackage; import resources.objects.building.BuildingObject; import resources.objects.creature.CreatureObject; import resources.objects.tangible.TangibleObject; +import services.chat.Mail; import services.sui.SUIWindow; import services.sui.SUIWindow.SUICallback; import services.sui.SUIWindow.Trigger; @@ -48,7 +52,7 @@ public class PlayerCityService implements INetworkDispatch { private NGECore core; private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - + private Random random = new Random(); private Vector playerCities = new Vector(); private int cityID = 0; // This must be persisted or handled via objectservice somehow @@ -87,10 +91,22 @@ public class PlayerCityService implements INetworkDispatch { } } - public PlayerCity addNewPlayerCity(CreatureObject founder) { + private long generateCityId() { + boolean found = false; + long id = 0; + while(!found) { + id = random.nextLong(); + final long cityId = id; + if(!playerCities.stream().filter(c -> c.getCityID() == cityId).findFirst().isPresent()) + found = true; + } + return id; + } + + public PlayerCity addNewPlayerCity(CreatureObject founder, BuildingObject cityHall) { PlayerCity newCity = null; synchronized(playerCities){ - newCity = new PlayerCity(founder,cityID++); + newCity = new PlayerCity(founder,cityID++, cityHall); playerCities.add(newCity); } newCitySUI1(founder,newCity); @@ -185,7 +201,7 @@ public class PlayerCityService implements INetworkDispatch { PlayerCity sandboxCity = null; synchronized(playerCities){ - sandboxCity = new PlayerCity(founder,cityID++); + sandboxCity = new PlayerCity(founder,cityID++, cityHall); sandboxCity.setCityName("Sandbox City"); playerCities.add(sandboxCity); } @@ -268,5 +284,34 @@ public class PlayerCityService implements INetworkDispatch { public void shutdown() { // TODO Auto-generated method stub + } + + public void destroyCity(PlayerCity city) { + + CreatureObject mayor = core.objectService.getObject(city.getMayorID()) == null ? core.objectService.getCreatureFromDB(city.getMayorID()) : (CreatureObject) core.objectService.getObject(city.getMayorID()); + if(mayor == null) + return; + + Mail mail = new Mail(); + mail.setMailId(NGECore.getInstance().chatService.generateMailId()); + mail.setRecieverId(city.getMayorID()); + mail.setStatus(Mail.NEW); + mail.setTimeStamp((int) (new Date().getTime() / 1000)); + mail.setSubject("@city/city:new_city_fail_subject"); + mail.setSenderName("@city/city:new_city_from"); + mail.setMessage("@city/city:new_city_fail_body"); + + if(mayor.getClient() != null) + core.chatService.sendPersistentMessageHeader(mayor.getClient(), mail); + core.chatService.storePersistentMessage(mail); + for(long civicStructureId : new Vector(city.getPlacedStructures())) { + BuildingObject civicStructure = (BuildingObject) core.objectService.getObject(civicStructureId); + if(civicStructure == null) + continue; + core.housingService.destroyStructure(civicStructure); + } + + playerCities.remove(city); + } }