Compare commits
44
Commits
No files matched your search
@@ -11,6 +11,7 @@ package engine.InterestManagement;
|
||||
import engine.gameManager.ZoneManager;
|
||||
import engine.math.Vector2f;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import engine.objects.Zone;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
@@ -46,7 +47,7 @@ public class Terrain {
|
||||
// Configure PLANAR zones to use the same 16x16 pixel image
|
||||
// that all similar terrains share. (See JSON)
|
||||
|
||||
if (this.zone.template.terrain_type.equals("PLANAR"))
|
||||
if (this.zone.template.terrain_type == mbEnums.TerrainGenType.PLANAR)
|
||||
this.heightmap = 1006300; // all 0
|
||||
|
||||
// Load pixel data for this terrain from cache
|
||||
@@ -71,8 +72,8 @@ public class Terrain {
|
||||
// the blending area between child and parent terrains when
|
||||
// they are stitched together.
|
||||
|
||||
this.blend_values.x = this.zone.template.max_blend;
|
||||
this.blend_values.y = this.zone.template.min_blend;
|
||||
this.blend_values.x = this.zone.template.zone_max_blend;
|
||||
this.blend_values.y = this.zone.template.zone_min_blend;
|
||||
|
||||
Vector2f major_blend = new Vector2f(this.blend_values.x / this.zone.major_radius,
|
||||
this.blend_values.y / this.zone.major_radius);
|
||||
@@ -217,8 +218,8 @@ public class Terrain {
|
||||
|
||||
// Normalize terrain offset
|
||||
|
||||
Vector2f normalizedOffset = new Vector2f(Math.abs(zone_offset.x) / this.zone.template.major_radius,
|
||||
Math.abs(zone_offset.y) / this.zone.template.minor_radius);
|
||||
Vector2f normalizedOffset = new Vector2f(Math.abs(zone_offset.x) / this.zone.template.zone_major_radius,
|
||||
Math.abs(zone_offset.y) / this.zone.template.zone_minor_radius);
|
||||
|
||||
float blendCoefficient;
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ public class PvpRecord extends DataRecord {
|
||||
outStatement.setString(7, DataWarehouse.hasher.encrypt(victimGuild.getNation().getObjectUUID()));
|
||||
outStatement.setInt(8, this.victim.getLevel());
|
||||
|
||||
outStatement.setString(9, DataWarehouse.hasher.encrypt(zone.getObjectUUID()));
|
||||
outStatement.setString(9, DataWarehouse.hasher.encryptObjectID(zone.getObjectUUID()));
|
||||
outStatement.setString(10, zone.zoneName);
|
||||
outStatement.setFloat(11, this.location.getX());
|
||||
outStatement.setFloat(12, -this.location.getZ()); // flip sign on 'y' coordinate
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class dbBuildingHandler extends dbHandlerBase {
|
||||
@@ -37,56 +38,55 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
this.localObjectType = mbEnums.GameObjectType.valueOf(this.localClass.getSimpleName());
|
||||
}
|
||||
|
||||
public Building CREATE_BUILDING(int parentZoneID, int OwnerUUID, String name, int meshUUID,
|
||||
Vector3fImmutable location, float meshScale, int currentHP,
|
||||
ProtectionState protectionState, int currentGold, int rank,
|
||||
DateTime upgradeDate, int blueprintUUID, float w, float rotY) {
|
||||
public int PERSIST(Building building) {
|
||||
|
||||
Building building = null;
|
||||
int objectUUID = 0;
|
||||
|
||||
if (DbManager.refuseStaticWrite(building))
|
||||
return 0;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("CALL `building_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?);")) {
|
||||
|
||||
preparedStatement.setInt(1, parentZoneID);
|
||||
preparedStatement.setInt(2, OwnerUUID);
|
||||
preparedStatement.setString(3, name);
|
||||
preparedStatement.setInt(4, meshUUID);
|
||||
preparedStatement.setFloat(5, location.x);
|
||||
preparedStatement.setFloat(6, location.y);
|
||||
preparedStatement.setFloat(7, location.z);
|
||||
preparedStatement.setFloat(8, meshScale);
|
||||
preparedStatement.setInt(9, currentHP);
|
||||
preparedStatement.setString(10, protectionState.name());
|
||||
preparedStatement.setInt(11, currentGold);
|
||||
preparedStatement.setInt(12, rank);
|
||||
preparedStatement.setInt(1, building.parentZoneUUID);
|
||||
preparedStatement.setInt(2, building.getOwnerUUID());
|
||||
preparedStatement.setString(3, building.getName());
|
||||
preparedStatement.setInt(4, building.meshUUID);
|
||||
preparedStatement.setFloat(5, building.statLat);
|
||||
preparedStatement.setFloat(6, building.statAlt);
|
||||
preparedStatement.setFloat(7, building.statLon);
|
||||
preparedStatement.setFloat(8, building.getMeshScale().x);
|
||||
preparedStatement.setInt(9, (int) building.getHealth());
|
||||
preparedStatement.setString(10, building.getProtectionState().name());
|
||||
preparedStatement.setInt(11, building.getStrongboxValue());
|
||||
preparedStatement.setInt(12, building.getRank());
|
||||
|
||||
if (upgradeDate != null)
|
||||
preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis()));
|
||||
if (building.upgradeDateTime != null)
|
||||
preparedStatement.setTimestamp(13, java.sql.Timestamp.valueOf(building.upgradeDateTime));
|
||||
else
|
||||
preparedStatement.setNull(13, java.sql.Types.DATE);
|
||||
|
||||
preparedStatement.setInt(14, blueprintUUID);
|
||||
preparedStatement.setFloat(15, w);
|
||||
preparedStatement.setFloat(16, rotY);
|
||||
preparedStatement.setInt(14, building.getBlueprintUUID());
|
||||
preparedStatement.setFloat(15, building.getRotation().w);
|
||||
preparedStatement.setFloat(16, building.getRotation().y);
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
if (rs.next()) {
|
||||
int objectUUID = (int) rs.getLong("UID");
|
||||
|
||||
if (objectUUID > 0)
|
||||
building = GET_BUILDINGBYUUID(objectUUID);
|
||||
}
|
||||
if (rs.next())
|
||||
objectUUID = (int) rs.getLong("UID");
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
return building;
|
||||
return objectUUID;
|
||||
}
|
||||
|
||||
public boolean DELETE_FROM_DATABASE(final Building b) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(b))
|
||||
return false;
|
||||
|
||||
return removeFromBuildings(b);
|
||||
}
|
||||
|
||||
@@ -107,6 +107,26 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
return buildings;
|
||||
}
|
||||
|
||||
public HashSet<Integer> GET_NPC_BUILDING_OWNERS() {
|
||||
|
||||
HashSet<Integer> npcOwners = new HashSet<>();
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT DISTINCT `obj_building`.`ownerUUID` FROM `obj_building` INNER JOIN `object` ON `object`.`UID` = `obj_building`.`ownerUUID` WHERE `object`.`type` = 'npc';")) {
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
while (rs.next())
|
||||
npcOwners.add(rs.getInt("ownerUUID"));
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
Logger.info("read: " + npcOwners.size() + " NPC building owners");
|
||||
return npcOwners;
|
||||
}
|
||||
|
||||
public Building GET_BUILDINGBYUUID(int uuid) {
|
||||
|
||||
if (uuid == 0)
|
||||
@@ -157,6 +177,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
String result = "";
|
||||
|
||||
if (DbManager.refuseStaticWrite(b))
|
||||
return result;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("CALL building_SETPROP(?,?,?)")) {
|
||||
|
||||
@@ -180,6 +203,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
int rowCount;
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return 0;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `object` INNER JOIN `obj_building` On `object`.`UID` = `obj_building`.`UID` SET `object`.`parent`=?, `obj_building`.`locationX`=?, `obj_building`.`locationY`=?, `obj_building`.`locationZ`=? WHERE `obj_building`.`UID`=?;")) {
|
||||
|
||||
@@ -200,23 +226,14 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
}
|
||||
|
||||
private boolean removeFromBuildings(final Building b) {
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ?")) {
|
||||
|
||||
preparedStatement.setLong(1, b.getObjectUUID());
|
||||
preparedStatement.execute();
|
||||
|
||||
return true;
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
return false;
|
||||
return deleteObjectTree(b.getObjectUUID(), null) >= 0;
|
||||
}
|
||||
|
||||
public boolean CHANGE_NAME(Building b, String newName) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(b))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `name`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -233,6 +250,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean SET_RESERVE(Building b, int reserveAmount) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(b))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `reserve`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -251,6 +271,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
//CAS update to rank
|
||||
public boolean CHANGE_RANK(final long buildingID, int newRank) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `rank`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -267,6 +290,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_BUILDING_HEALTH(final long buildingID, int NewHealth) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `currentHP`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -283,6 +309,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_BUILDING_ALTITUDE(final long buildingID, float newAltitude) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `locationY`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -299,6 +328,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_PROTECTIONSTATE(final long buildingUUID, ProtectionState protectionState) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingUUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `protectionState`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -315,6 +347,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_DOOR_LOCK(final int buildingUUID, int doorFlags) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingUUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET doorState = ? WHERE UID = ?")) {
|
||||
|
||||
@@ -331,6 +366,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean ADD_TO_FRIENDS_LIST(final long buildingID, final long friendID, final long guildID, final int friendType) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_building_friends` (`buildingUID`, `playerUID`,`guildUID`, `friendType`) VALUES (?,?,?,?)")) {
|
||||
|
||||
@@ -349,6 +387,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean REMOVE_FROM_FRIENDS_LIST(final long buildingID, long friendID, long guildID, int friendType) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_friends` WHERE `buildingUID`=? AND `playerUID`=? AND `guildUID` =? AND `friendType` = ?")) {
|
||||
|
||||
@@ -367,6 +408,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean REMOVE_FROM_CONDEMNED_LIST(final long buildingID, long friendID, long guildID, int friendType) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_condemned` WHERE `buildingUID`=? AND `playerUID`=? AND `guildUID` =? AND `friendType` = ?")) {
|
||||
|
||||
@@ -385,6 +429,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public void CLEAR_FRIENDS_LIST(final long buildingID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_friends` WHERE `buildingUID`=?")) {
|
||||
|
||||
@@ -399,6 +446,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public void CLEAR_CONDEMNED_LIST(final long buildingID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_condemned` WHERE `buildingUID`=?")) {
|
||||
|
||||
@@ -413,6 +463,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean CLEAR_PATROL(final long buildingID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, buildingID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_building_patrol_points` WHERE `buildingUID`=?")) {
|
||||
|
||||
@@ -514,6 +567,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean ADD_TO_CONDEMNLIST(final long parentUID, final long playerUID, final long guildID, final int friendType) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, parentUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_building_condemned` (`buildingUID`, `playerUID`,`guildUID`, `friendType`) VALUES (?,?,?,?)")) {
|
||||
|
||||
@@ -532,6 +588,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean ADD_TO_PATROL(final long parentUID, final Vector3fImmutable patrolPoint) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, parentUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_building_patrol_points` (`buildingUID`, `patrolX`,`patrolY`, `patrolZ`) VALUES (?,?,?,?)")) {
|
||||
|
||||
@@ -677,6 +736,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateBuildingRank(final Building b, int Rank) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(b))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `rank`=?,"
|
||||
+ "`upgradeDate`=?, `meshUUID`=?, `currentHP`=? "
|
||||
@@ -698,6 +760,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateReverseKOS(final Building b, boolean reverse) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(b))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `reverseKOS`=? WHERE `UID` = ?")) {
|
||||
|
||||
@@ -714,6 +779,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateActiveCondemn(final Condemned condemn, boolean active) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Building, condemn.buildingUUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_building_condemned` SET `active`=? "
|
||||
+ "WHERE`buildingUID` = ? AND `playerUID` = ? AND `guildUID` = ? AND `friendType` = ?")) {
|
||||
@@ -734,6 +802,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateBuildingOwner(final Building building, int ownerUUID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(building))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_building` SET `ownerUUID`=? "
|
||||
+ " WHERE `UID` = ?")) {
|
||||
@@ -751,6 +822,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateBuildingUpgradeTime(LocalDateTime upgradeDateTime, Building toUpgrade, int costToUpgrade) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(toUpgrade))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET upgradeDate=?, currentGold=? "
|
||||
+ "WHERE UID = ?")) {
|
||||
@@ -774,6 +848,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateMaintDate(Building building) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(building))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET maintDate=? "
|
||||
+ "WHERE UID = ?")) {
|
||||
@@ -795,6 +872,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean addTaxes(Building building, TaxType taxType, int amount, boolean enforceKOS) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(building))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxType=?, taxAmount = ?, enforceKOS = ? "
|
||||
+ "WHERE UID = ?")) {
|
||||
@@ -814,6 +894,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean removeTaxes(Building building) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(building))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxType=?, taxAmount = ?, enforceKOS = ?, taxDate = ? "
|
||||
+ "WHERE UID = ?")) {
|
||||
@@ -834,6 +917,9 @@ public class dbBuildingHandler extends dbHandlerBase {
|
||||
|
||||
public boolean acceptTaxes(Building building) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(building))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxDate=? "
|
||||
+ "WHERE UID = ?")) {
|
||||
|
||||
@@ -139,6 +139,9 @@ public class dbCityHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateforceRename(City city, boolean value) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(city))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `forceRename`=?"
|
||||
+ " WHERE `UID` = ?")) {
|
||||
@@ -156,6 +159,9 @@ public class dbCityHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateOpenCity(City city, boolean value) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(city))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `open`=?"
|
||||
+ " WHERE `UID` = ?")) {
|
||||
@@ -173,6 +179,9 @@ public class dbCityHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateTOL(City city, int tolID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(city))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `treeOfLifeUUID`=?"
|
||||
+ " WHERE `UID` = ?")) {
|
||||
@@ -191,6 +200,9 @@ public class dbCityHandler extends dbHandlerBase {
|
||||
|
||||
public boolean renameCity(City city, String name) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(city))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `name`=?"
|
||||
+ " WHERE `UID` = ?")) {
|
||||
@@ -209,6 +221,9 @@ public class dbCityHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateSiegesWithstood(City city, int value) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(city))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `name`=?"
|
||||
+ " WHERE `UID` = ?")) {
|
||||
@@ -227,6 +242,9 @@ public class dbCityHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateRealmTaxDate(City city, LocalDateTime localDateTime) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(city))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_city` SET `realmTaxDate` =?"
|
||||
+ " WHERE `UID` = ?")) {
|
||||
|
||||
@@ -95,6 +95,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean ADD_TO_BANISHED_FROM_GUILDLIST(int target, long characterID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, target))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_guild_banishlist` (`GuildID`, `CharacterID`) VALUES (?,?)")) {
|
||||
|
||||
@@ -111,6 +114,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean REMOVE_FROM_BANISH_LIST(int target, long characterID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, target))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_guild_banishlist` (`GuildID`, `CharacterID`) VALUES (?,?)")) {
|
||||
|
||||
@@ -358,6 +364,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean SET_GUILD_OWNED_CITY(int guildID, int cityID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, guildID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_guild` SET `ownedCity`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -374,6 +383,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean SET_GUILD_LEADER(int objectUUID, int guildID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, guildID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_guild` SET `leaderUID`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -413,6 +425,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
Guild guild = null;
|
||||
GuildTag guildTag = g.getGuildTag();
|
||||
|
||||
if (DbManager.refuseStaticWrite(g))
|
||||
return null;
|
||||
|
||||
if (guildTag == null)
|
||||
return null;
|
||||
|
||||
@@ -465,6 +480,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_PARENT(int guildUID, int parentUID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, guildUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `object` SET `parent`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -483,20 +501,19 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
int row_count = 0;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ?")) {
|
||||
if (DbManager.refuseStaticWrite(guild))
|
||||
return 0;
|
||||
|
||||
preparedStatement.setLong(1, guild.getObjectUUID());
|
||||
row_count = preparedStatement.executeUpdate();
|
||||
row_count = Math.max(deleteObjectTree(guild.getObjectUUID(), null), 0);
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
return row_count;
|
||||
}
|
||||
|
||||
public boolean UPDATE_MINETIME(int guildUID, int mineTime) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, guildUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_guild` SET `mineTime`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -564,6 +581,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateDatabase(final Guild g) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(g))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_guild` SET `name`=?, `backgroundColor01`=?, `backgroundColor02`=?, `symbolColor`=?, `backgroundDesign`=?, `symbol`=?, `charter`=?, `motd`=?, `icMotd`=?, `nationMotd`=?, `leaderUID`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -590,6 +610,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean ADD_TO_ALLIANCE_LIST(final long sourceGuildID, final long targetGuildID, boolean isRecommended, boolean isAlly, String recommender) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, sourceGuildID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_guild_allianceenemylist` (`GuildID`, `OtherGuildID`,`isRecommended`, `isAlliance`, `recommender`) VALUES (?,?,?,?,?)")) {
|
||||
|
||||
@@ -609,6 +632,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean REMOVE_FROM_ALLIANCE_LIST(final long sourceGuildID, long targetGuildID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, sourceGuildID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_guild_allianceenemylist` WHERE `GuildID`=? AND `OtherGuildID`=?")) {
|
||||
|
||||
@@ -625,6 +651,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_RECOMMENDED(final long sourceGuildID, long targetGuildID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, sourceGuildID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_guild_allianceenemylist` SET `isRecommended` = ? WHERE `GuildID`=? AND `OtherGuildID`=?")) {
|
||||
|
||||
@@ -641,6 +670,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_ALLIANCE(final long sourceGuildID, long targetGuildID, boolean isAlly) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, sourceGuildID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_guild_allianceenemylist` SET `isAlliance` = ? WHERE `GuildID`=? AND `OtherGuildID`=?")) {
|
||||
|
||||
@@ -657,6 +689,9 @@ public class dbGuildHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_ALLIANCE_AND_RECOMMENDED(final long sourceGuildID, long targetGuildID, boolean isAlly) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Guild, sourceGuildID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_guild_allianceenemylist` SET `isRecommended` = ?, `isAlliance` = ? WHERE `GuildID`=? AND `OtherGuildID`=?")) {
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ import engine.objects.AbstractGameObject;
|
||||
import engine.objects.AbstractWorldObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public abstract class dbHandlerBase {
|
||||
@@ -77,4 +80,51 @@ public abstract class dbHandlerBase {
|
||||
}
|
||||
return objectList;
|
||||
}
|
||||
|
||||
// Deletes an object row and every row below it by object.parent, as the dropped ref_parent cascade did
|
||||
// (World Boot Plan 2.4). typeFilter limits the root row to one object type; null deletes any type.
|
||||
// Returns the number of root rows deleted.
|
||||
|
||||
protected int deleteObjectTree(long objectUID, String typeFilter) {
|
||||
|
||||
String descendantSql = "DELETE FROM `object` WHERE `UID` IN (SELECT `UID` FROM (WITH RECURSIVE `tree` AS "
|
||||
+ "(SELECT `UID` FROM `object` WHERE `parent` = ? UNION ALL "
|
||||
+ "SELECT `object`.`UID` FROM `object` INNER JOIN `tree` ON `object`.`parent` = `tree`.`UID`) "
|
||||
+ "SELECT `UID` FROM `tree`) AS `descendants`)";
|
||||
String rootSql = typeFilter == null ? "DELETE FROM `object` WHERE `UID` = ?"
|
||||
: "DELETE FROM `object` WHERE `UID` = ? AND `type` = ?";
|
||||
int rootCount = 0;
|
||||
|
||||
try (Connection connection = DbManager.getConnection()) {
|
||||
|
||||
connection.setAutoCommit(false);
|
||||
|
||||
try (PreparedStatement descendantStatement = connection.prepareStatement(descendantSql);
|
||||
PreparedStatement rootStatement = connection.prepareStatement(rootSql)) {
|
||||
|
||||
descendantStatement.setLong(1, objectUID);
|
||||
descendantStatement.executeUpdate();
|
||||
|
||||
rootStatement.setLong(1, objectUID);
|
||||
|
||||
if (typeFilter != null)
|
||||
rootStatement.setString(2, typeFilter);
|
||||
|
||||
rootCount = rootStatement.executeUpdate();
|
||||
connection.commit();
|
||||
|
||||
} catch (SQLException e) {
|
||||
connection.rollback();
|
||||
throw e;
|
||||
} finally {
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return rootCount;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,9 @@ public class dbMobHandler extends dbHandlerBase {
|
||||
|
||||
Mob mobile = null;
|
||||
|
||||
if (DbManager.refuseStaticWrite(toAdd))
|
||||
return null;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("CALL `mob_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")) {
|
||||
|
||||
@@ -84,6 +87,9 @@ public class dbMobHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateUpgradeTime(Mob mob, DateTime upgradeDateTime) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mob))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_mob SET upgradeDate=? "
|
||||
+ "WHERE UID = ?")) {
|
||||
@@ -106,16 +112,12 @@ public class dbMobHandler extends dbHandlerBase {
|
||||
public int DELETE_MOB(final Mob mob) {
|
||||
|
||||
int row_count = 0;
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ?")) {
|
||||
|
||||
preparedStatement.setLong(1, mob.getDBID());
|
||||
row_count = preparedStatement.executeUpdate();
|
||||
if (DbManager.refuseStaticWrite(mob))
|
||||
return 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
row_count = Math.max(deleteObjectTree(mob.getDBID(), null), 0);
|
||||
|
||||
}
|
||||
return row_count;
|
||||
}
|
||||
|
||||
@@ -146,6 +148,9 @@ public class dbMobHandler extends dbHandlerBase {
|
||||
|
||||
public boolean ADD_GUARD_MINION(final long captainUID, final String minionName) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Mob, captainUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_guards` (`captainUID`, `minionName`) VALUES (?,?)")) {
|
||||
|
||||
@@ -162,6 +167,9 @@ public class dbMobHandler extends dbHandlerBase {
|
||||
|
||||
public boolean REMOVE_GUARD_MINION(final long captainUID, final String minionName) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Mob, captainUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_guards` WHERE `captainUID`=? AND `minionName`=? LIMIT 1;")) {
|
||||
|
||||
@@ -178,6 +186,9 @@ public class dbMobHandler extends dbHandlerBase {
|
||||
|
||||
public boolean REMOVE_ALL_MINIONS(final long captainUID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Mob, captainUID))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `dyn_guards` WHERE `captainUID`=?;")) {
|
||||
|
||||
@@ -212,6 +223,9 @@ public class dbMobHandler extends dbHandlerBase {
|
||||
|
||||
int row_count = 0;
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.Mob, mobID))
|
||||
return 0;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `object` INNER JOIN `obj_mob` On `object`.`UID` = `obj_mob`.`UID` SET `object`.`parent`=?, `obj_mob`.`mob_spawnX`=?, `obj_mob`.`mob_spawnY`=?, `obj_mob`.`mob_spawnZ`=? WHERE `obj_mob`.`UID`=?;")) {
|
||||
|
||||
@@ -234,6 +248,9 @@ public class dbMobHandler extends dbHandlerBase {
|
||||
|
||||
String result = "";
|
||||
|
||||
if (DbManager.refuseStaticWrite(m))
|
||||
return result;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("CALL mob_SETPROP(?,?,?)")) {
|
||||
|
||||
|
||||
@@ -75,6 +75,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
NPC npc = null;
|
||||
|
||||
if (DbManager.refuseStaticWrite(toAdd))
|
||||
return null;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("CALL `npc_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")) {
|
||||
|
||||
@@ -120,16 +123,11 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
npc.removeFromZone();
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ?")) {
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return 0;
|
||||
|
||||
preparedStatement.setLong(1, npc.getDBID());
|
||||
row_count = preparedStatement.executeUpdate();
|
||||
row_count = Math.max(deleteObjectTree(npc.getDBID(), null), 0);
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
|
||||
}
|
||||
return row_count;
|
||||
}
|
||||
|
||||
@@ -172,6 +170,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
int rowCount;
|
||||
|
||||
if (DbManager.refuseStaticWrite(mbEnums.GameObjectType.NPC, npcID))
|
||||
return 0;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `object` INNER JOIN `obj_npc` On `object`.`UID` = `obj_npc`.`UID` SET `object`.`parent`=?, `obj_npc`.`npc_spawnX`=?, `obj_npc`.`npc_spawnY`=?, `obj_npc`.`npc_spawnZ`=? WHERE `obj_npc`.`UID`=?;")) {
|
||||
|
||||
@@ -195,6 +196,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
String result = "";
|
||||
|
||||
if (DbManager.refuseStaticWrite(n))
|
||||
return result;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("CALL npc_SETPROP(?,?,?)")) {
|
||||
|
||||
@@ -215,6 +219,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
public void updateDatabase(final NPC npc) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_npc SET npc_name=?, npc_contractID=?, npc_typeID=?, npc_guildID=?,"
|
||||
+ " npc_spawnX=?, npc_spawnY=?, npc_spawnZ=?, npc_level=? ,"
|
||||
@@ -242,6 +249,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
public boolean updateUpgradeTime(NPC npc, DateTime upgradeDateTime) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_npc SET upgradeDate=? "
|
||||
+ "WHERE UID = ?")) {
|
||||
@@ -264,6 +274,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_MOBBASE(NPC npc, int mobBaseID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_npc` SET `npc_raceID`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -280,6 +293,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_EQUIPSET(NPC npc, int equipSetID) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_npc` SET `equipsetID`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -296,6 +312,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_NAME(NPC npc, String name) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_npc` SET `npc_name`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -312,6 +331,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_RACE(NPC npc, int value) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `obj_npc` SET `npc_raceID`=? WHERE `UID`=?")) {
|
||||
|
||||
@@ -363,6 +385,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
public boolean UPDATE_PROFITS(NPC npc, ProfitType profitType, float value) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_npc_profits` SET `" + profitType.dbField + "` = ? WHERE `npcUID`=?")) {
|
||||
|
||||
@@ -398,6 +423,9 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
|
||||
public boolean CREATE_PROFITS(NPC npc) {
|
||||
|
||||
if (DbManager.refuseStaticWrite(npc))
|
||||
return false;
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_npc_profits` (`npcUID`) VALUES (?)")) {
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.db.handlers;
|
||||
|
||||
import engine.gameManager.DbManager;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class dbStaticWorldHandler extends dbHandlerBase {
|
||||
|
||||
public dbStaticWorldHandler() {
|
||||
|
||||
}
|
||||
|
||||
// Table and column come from StaticManifest's fixed list, never from input. -1 means "none" and is skipped.
|
||||
public HashMap<Integer, Integer> GET_NEGATIVE_REFERENCES(String table, String column) {
|
||||
|
||||
HashMap<Integer, Integer> rowsByValue = new HashMap<>();
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `" + column + "` AS `value`, COUNT(*) AS `rows` FROM `"
|
||||
+ table + "` WHERE `" + column + "` < -1 GROUP BY `" + column + "`;")) {
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
while (rs.next())
|
||||
rowsByValue.put(rs.getInt("value"), rs.getInt("rows"));
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(table + "." + column + ": " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return rowsByValue;
|
||||
}
|
||||
|
||||
// Leader NPC of each DB NPC guild by guild name. Interim owner source for static buildings until the
|
||||
// static guilds get their own leaders (World Boot Plan 2.5/3.2).
|
||||
public HashMap<String, Integer> GET_NPC_GUILD_LEADERS() {
|
||||
|
||||
HashMap<String, Integer> leadersByName = new HashMap<>();
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_guild`.`name`, `obj_guild`.`leaderUID` FROM `obj_guild` "
|
||||
+ "INNER JOIN `object` ON `object`.`UID` = `obj_guild`.`leaderUID` WHERE `object`.`type` = 'npc';")) {
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
while (rs.next())
|
||||
leadersByName.put(rs.getString("name"), rs.getInt("leaderUID"));
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
return leadersByName;
|
||||
}
|
||||
|
||||
// obj_zone rows built by the import procedures; 0 once the 2.4 migration has run, -1 on error.
|
||||
public int COUNT_STATIC_ZONE_ROWS() {
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT COUNT(*) AS `zones` FROM `obj_zone` WHERE `template` <> 0;")) {
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
if (rs.next())
|
||||
return rs.getInt("zones");
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// NPC cities still in obj_city; 0 once the 2.5 migration has run, -1 on error.
|
||||
public int COUNT_NPC_CITY_ROWS() {
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT COUNT(*) AS `cities` FROM `obj_city` WHERE `isNpc` = 1;")) {
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
if (rs.next())
|
||||
return rs.getInt("cities");
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,8 @@
|
||||
package engine.db.handlers;
|
||||
|
||||
import engine.gameManager.DbManager;
|
||||
import engine.gameManager.ZoneManager;
|
||||
import engine.mbEnums;
|
||||
import engine.objects.Zone;
|
||||
import engine.objects.ZoneTemplate;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.sql.Connection;
|
||||
@@ -46,6 +44,23 @@ public class dbZoneHandler extends dbHandlerBase {
|
||||
return zoneList;
|
||||
}
|
||||
|
||||
public ArrayList<Zone> GET_PLAYER_CITY_ZONES() {
|
||||
|
||||
ArrayList<Zone> zoneList = new ArrayList<>();
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `obj_zone`.*, `object`.`parent` FROM `object` INNER JOIN `obj_zone` ON `obj_zone`.`UID` = `object`.`UID` WHERE `obj_zone`.`template` = 0 ORDER BY `object`.`UID` ASC;")) {
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
zoneList = getObjectsFromRs(rs, 200);
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
return zoneList;
|
||||
}
|
||||
|
||||
public Zone GET_BY_UID(long ID) {
|
||||
|
||||
Zone zone = (Zone) DbManager.getFromCache(mbEnums.GameObjectType.Zone, (int) ID);
|
||||
@@ -68,54 +83,12 @@ public class dbZoneHandler extends dbHandlerBase {
|
||||
return zone;
|
||||
}
|
||||
|
||||
public void LOAD_ALL_ZONE_TEMPLATES() {
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_zone_templates")) {
|
||||
|
||||
ResultSet rs = preparedStatement.executeQuery();
|
||||
|
||||
while (rs.next()) {
|
||||
ZoneTemplate zoneTemplate = new ZoneTemplate(rs);
|
||||
ZoneManager._zone_templates.put(zoneTemplate.templateID, zoneTemplate);
|
||||
}
|
||||
|
||||
// Add player city
|
||||
|
||||
ZoneTemplate zoneTemplate = new ZoneTemplate();
|
||||
|
||||
zoneTemplate.templateID = 0;
|
||||
zoneTemplate.sea_level_type = "PARENT";
|
||||
zoneTemplate.sea_level = 0;
|
||||
zoneTemplate.max_blend = 128;
|
||||
zoneTemplate.min_blend = 128;
|
||||
zoneTemplate.terrain_max_y = 5;
|
||||
zoneTemplate.major_radius = (int) mbEnums.CityBoundsType.ZONE.halfExtents;
|
||||
zoneTemplate.minor_radius = (int) mbEnums.CityBoundsType.ZONE.halfExtents;
|
||||
zoneTemplate.terrain_type = "PLANAR";
|
||||
|
||||
ZoneManager._zone_templates.put(zoneTemplate.templateID, zoneTemplate);
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean DELETE_ZONE(final Zone zone) {
|
||||
|
||||
try (Connection connection = DbManager.getConnection();
|
||||
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `object` WHERE `UID` = ? AND `type` = 'zone'")) {
|
||||
|
||||
preparedStatement.setInt(1, zone.getObjectUUID());
|
||||
|
||||
return (preparedStatement.executeUpdate() > 0);
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
if (DbManager.refuseStaticWrite(zone))
|
||||
return false;
|
||||
|
||||
return deleteObjectTree(zone.getObjectUUID(), "zone") > 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import engine.gameManager.ChatManager;
|
||||
import engine.gameManager.DbManager;
|
||||
import engine.mbEnums.GameObjectType;
|
||||
import engine.objects.*;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -99,12 +100,18 @@ public abstract class AbstractDevCmd {
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("?")) {
|
||||
this.sendHelp(pcSender);
|
||||
this.sendUsage(pcSender);
|
||||
} else if (this.addsWorldContent()) {
|
||||
this.refuseWorldChange(pcSender, target);
|
||||
} else {
|
||||
this.tr = target;
|
||||
this._doCmd(pcSender, args, target);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean addsWorldContent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void _doCmd(PlayerCharacter pcSender, String[] args,
|
||||
AbstractGameObject target);
|
||||
|
||||
@@ -178,4 +185,22 @@ public abstract class AbstractDevCmd {
|
||||
ChatManager.chatSystemInfo(pc, msgText);
|
||||
}
|
||||
|
||||
// Static world objects are built from client data each boot, so they are changed with external tooling only.
|
||||
protected boolean refuseStaticTarget(PlayerCharacter pc, AbstractGameObject target) {
|
||||
|
||||
if (!(target instanceof AbstractWorldObject) || !((AbstractWorldObject) target).isStaticWorldObject())
|
||||
return false;
|
||||
|
||||
this.refuseWorldChange(pc, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void refuseWorldChange(PlayerCharacter pc, AbstractGameObject target) {
|
||||
|
||||
String targetText = (target == null) ? "no target" : target.getObjectType() + " " + target.getObjectUUID();
|
||||
|
||||
this.throwbackError(pc, "Must use external tooling to modify gameworld");
|
||||
Logger.info("Refused dev command " + this.getMainCmdString() + " on " + targetText + " by " + pc.getFirstName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import engine.devcmd.AbstractDevCmd;
|
||||
import engine.gameManager.ChatManager;
|
||||
import engine.gameManager.DbManager;
|
||||
import engine.gameManager.ZoneManager;
|
||||
import engine.math.Vector3f;
|
||||
import engine.math.Quaternion;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums.ProtectionState;
|
||||
import engine.objects.*;
|
||||
@@ -54,8 +54,7 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
||||
"Invalid addBuilding Command. Invalid Building ID.");
|
||||
return;
|
||||
}
|
||||
Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f);
|
||||
float w = 1f;
|
||||
Quaternion rotation = new Quaternion();
|
||||
Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
|
||||
|
||||
if (zone == null) {
|
||||
@@ -73,8 +72,7 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
||||
Building likeBuilding = DbManager.BuildingQueries.GET_BUILDING_BY_MESH(ID);
|
||||
|
||||
if (likeBuilding != null) {
|
||||
rot = likeBuilding.getRot();
|
||||
w = likeBuilding.getw();
|
||||
rotation = likeBuilding.getRotation();
|
||||
}
|
||||
|
||||
String buildingName = "";
|
||||
@@ -90,11 +88,10 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
||||
blueprintUUID = blueprint.getBlueprintUUID();
|
||||
}
|
||||
|
||||
Building building = DbManager.BuildingQueries.
|
||||
CREATE_BUILDING(
|
||||
zone.getObjectUUID(), 0, buildingName, ID,
|
||||
Building building = Building.persistBuilding(Building.createBuilding(
|
||||
zone, 0, buildingName, ID,
|
||||
localLoc, 1.0f, 0, ProtectionState.PROTECTED, 0, rank,
|
||||
null, blueprintUUID, w, rot.y);
|
||||
null, blueprintUUID, rotation.w, rotation.y));
|
||||
|
||||
|
||||
if (building == null) {
|
||||
@@ -102,9 +99,6 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
||||
return;
|
||||
}
|
||||
|
||||
building.setRot(rot);
|
||||
building.setw(w);
|
||||
|
||||
building.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
|
||||
WorldGrid.addObject(building, pc);
|
||||
ChatManager.chatSayInfo(pc,
|
||||
@@ -114,6 +108,11 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean addsWorldContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String _getHelpString() {
|
||||
return "Creates a building of type 'buildingID' at the location your character is standing.";
|
||||
|
||||
@@ -75,6 +75,11 @@ public class AddMobCmd extends AbstractDevCmd {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean addsWorldContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String _getHelpString() {
|
||||
return "Creates a Mob of type 'mobID' at the location your character is standing";
|
||||
|
||||
@@ -94,6 +94,11 @@ public class AddNPCCmd extends AbstractDevCmd {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean addsWorldContent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String _getHelpString() {
|
||||
return "Creates an NPC of type 'npcID' at the location your character is standing";
|
||||
|
||||
@@ -37,8 +37,8 @@ public class GetHeightCmd extends AbstractDevCmd {
|
||||
|
||||
Vector2f childZoneLoc = ZoneManager.worldToTerrainSpace(playerCharacter.getLoc(), heightmapZone);
|
||||
Vector2f childZoneOffset = ZoneManager.worldToZoneOffset(playerCharacter.getLoc(), heightmapZone);
|
||||
Vector2f normalizedOffset = new Vector2f(Math.abs(childZoneOffset.x) / heightmapZone.template.major_radius,
|
||||
Math.abs(childZoneOffset.y) / heightmapZone.template.minor_radius);
|
||||
Vector2f normalizedOffset = new Vector2f(Math.abs(childZoneOffset.x) / heightmapZone.template.zone_major_radius,
|
||||
Math.abs(childZoneOffset.y) / heightmapZone.template.zone_minor_radius);
|
||||
Vector2f parentZoneLoc = ZoneManager.worldToTerrainSpace(playerCharacter.getLoc(), parentZone);
|
||||
|
||||
float childHeight = heightmapZone.terrain.getInterpolatedTerrainHeight(childZoneLoc);
|
||||
@@ -74,7 +74,7 @@ public class GetHeightCmd extends AbstractDevCmd {
|
||||
|
||||
this.throwbackInfo(playerCharacter, "Child Zone Offset: " + "[" + childZoneOffset.x + "]" + "[" + childZoneOffset.y + "]");
|
||||
this.throwbackInfo(playerCharacter, "Normalized offset: " + "[" + normalizedOffset.x + "]" + "[" + normalizedOffset.y + "]");
|
||||
this.throwbackInfo(playerCharacter, "template blend Values: (max/min): " + heightmapZone.template.max_blend + " /" + heightmapZone.template.min_blend);
|
||||
this.throwbackInfo(playerCharacter, "template blend Values: (max/min): " + heightmapZone.template.zone_max_blend + " /" + heightmapZone.template.zone_min_blend);
|
||||
this.throwbackInfo(playerCharacter, "terrain values (max/min): " + heightmapZone.terrain.blend_values.x + " /" + heightmapZone.terrain.blend_values.y);
|
||||
this.throwbackInfo(playerCharacter, "Blend coefficient: " + heightmapZone.terrain.getTerrainBlendCoefficient(childZoneOffset));
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import engine.mbEnums.GameObjectType;
|
||||
import engine.mbEnums.TargetColor;
|
||||
import engine.objects.*;
|
||||
import engine.parsers.objects.CObjectTemplate;
|
||||
import engine.server.MBServerStatics;
|
||||
import engine.util.StringUtils;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
@@ -88,7 +89,7 @@ public class InfoCmd extends AbstractDevCmd {
|
||||
output += newline;
|
||||
output += StringUtils.addWS("Alt: " + targetLoc.y, 20);
|
||||
output += newline;
|
||||
output += "Rot: " + targetAWO.getRot().y;
|
||||
output += "Rot: " + targetAWO.getRotation().y;
|
||||
output += newline;
|
||||
double radian = 0;
|
||||
|
||||
@@ -106,7 +107,7 @@ public class InfoCmd extends AbstractDevCmd {
|
||||
case Building:
|
||||
Building targetBuilding = (Building) target;
|
||||
output += StringUtils.addWS("Lac: "
|
||||
+ targetBuilding.getw(), 20);
|
||||
+ targetBuilding.getRotation().w, 20);
|
||||
output += "Blueprint : ";
|
||||
output += targetBuilding.getBlueprintUUID();
|
||||
output += newline;
|
||||
@@ -343,7 +344,7 @@ public class InfoCmd extends AbstractDevCmd {
|
||||
|
||||
case NPC:
|
||||
NPC targetNPC = (NPC) target;
|
||||
output += "databaseID: " + targetNPC.getDBID() + newline;
|
||||
output += "databaseID: " + databaseIDText(targetNPC.getDBID()) + newline;
|
||||
output += "Name: " + targetNPC.getName();
|
||||
output += newline;
|
||||
output += StringUtils.addWS("Level: " + targetNPC.getLevel(), 20);
|
||||
@@ -414,7 +415,7 @@ public class InfoCmd extends AbstractDevCmd {
|
||||
break;
|
||||
case Mob:
|
||||
Mob targetMob = (Mob) target;
|
||||
output += "databaseID: " + targetMob.getDBID() + newline;
|
||||
output += "databaseID: " + databaseIDText(targetMob.getDBID()) + newline;
|
||||
output += "Name: " + targetMob.getName();
|
||||
output += newline;
|
||||
output += StringUtils.addWS("Level: " + targetMob.getLevel(), 20);
|
||||
@@ -539,6 +540,14 @@ public class InfoCmd extends AbstractDevCmd {
|
||||
throwbackInfo(pc, output);
|
||||
}
|
||||
|
||||
private static String databaseIDText(int databaseID) {
|
||||
|
||||
if (databaseID == MBServerStatics.NO_DB_ROW_ASSIGNED_YET)
|
||||
return "none";
|
||||
|
||||
return String.valueOf(databaseID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String _getHelpString() {
|
||||
return "Gets information on an Object.";
|
||||
|
||||
@@ -66,6 +66,9 @@ public class PullCmd extends AbstractDevCmd {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.refuseStaticTarget(pcSender, target))
|
||||
return;
|
||||
|
||||
targetLoc = pcSender.getLoc();
|
||||
serverZone = ZoneManager.findSmallestZone(targetLoc);
|
||||
switch (target.getObjectType()) {
|
||||
|
||||
@@ -245,11 +245,17 @@ public class PurgeObjectsCmd extends AbstractDevCmd {
|
||||
|
||||
HashSet<AbstractWorldObject> objectList =
|
||||
WorldGrid.getObjectsInRangePartial(_currentLocation, _targetRange, _targetMask);
|
||||
int staticSkipped = 0;
|
||||
|
||||
// Iterate through array and remove objects from game world and database
|
||||
|
||||
for (AbstractWorldObject awo : objectList) {
|
||||
|
||||
if (awo.isStaticWorldObject()) {
|
||||
staticSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (awo.getObjectType()) {
|
||||
case Building:
|
||||
removeBuilding(pc, (Building) awo);
|
||||
@@ -266,7 +272,8 @@ public class PurgeObjectsCmd extends AbstractDevCmd {
|
||||
}
|
||||
|
||||
// Send results to user
|
||||
throwbackInfo(pc, "Purge: " + objectList.size() + " objects were removed in range " + _targetRange);
|
||||
throwbackInfo(pc, "Purge: " + (objectList.size() - staticSkipped) + " objects were removed in range " + _targetRange
|
||||
+ ", " + staticSkipped + " static objects skipped");
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,9 @@ public class RemoveObjectCmd extends AbstractDevCmd {
|
||||
|
||||
private void removeBuilding(PlayerCharacter pc, Building building) {
|
||||
|
||||
if (this.refuseStaticTarget(pc, building))
|
||||
return;
|
||||
|
||||
Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc());
|
||||
|
||||
if (currentZone == null) {
|
||||
@@ -164,6 +167,9 @@ public class RemoveObjectCmd extends AbstractDevCmd {
|
||||
|
||||
private void removeNPC(PlayerCharacter pc, NPC npc) {
|
||||
|
||||
if (this.refuseStaticTarget(pc, npc))
|
||||
return;
|
||||
|
||||
Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc());
|
||||
if (currentZone == null) {
|
||||
this.throwbackError(pc, "Could not locate zone for player.");
|
||||
@@ -196,6 +202,9 @@ public class RemoveObjectCmd extends AbstractDevCmd {
|
||||
|
||||
private void removeMob(PlayerCharacter pc, Mob mob) {
|
||||
|
||||
if (this.refuseStaticTarget(pc, mob))
|
||||
return;
|
||||
|
||||
Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc());
|
||||
|
||||
if (currentZone == null) {
|
||||
|
||||
@@ -47,6 +47,10 @@ public class RenameCmd extends AbstractDevCmd {
|
||||
building = (Building) target;
|
||||
} else
|
||||
npc = getTargetAsNPC(pcSender);
|
||||
|
||||
if (this.refuseStaticTarget(pcSender, npc) || this.refuseStaticTarget(pcSender, building))
|
||||
return;
|
||||
|
||||
if (npc != null) {
|
||||
DbManager.NPCQueries.SET_PROPERTY(npc, "npc_name", args[0]);
|
||||
String name = args[0];
|
||||
|
||||
@@ -14,7 +14,8 @@ import engine.devcmd.AbstractDevCmd;
|
||||
import engine.gameManager.BuildingManager;
|
||||
import engine.gameManager.ChatManager;
|
||||
import engine.gameManager.DbManager;
|
||||
import engine.math.Vector3f;
|
||||
import engine.math.FastMath;
|
||||
import engine.math.Quaternion;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.objects.*;
|
||||
|
||||
@@ -63,14 +64,12 @@ public class RotateCmd extends AbstractDevCmd {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3f rotation = new Vector3f(0f, rot, 0f);
|
||||
|
||||
if (target instanceof Building)
|
||||
rotateBuilding(pc, (Building) target, rotation, rot, false);
|
||||
rotateBuilding(pc, (Building) target, (float) Math.toRadians(rot));
|
||||
else if (target instanceof NPC)
|
||||
rotateNPC(pc, (NPC) target, rotation, false);
|
||||
rotateNPC(pc, (NPC) target, (float) Math.toRadians(rot));
|
||||
else if (target instanceof Mob)
|
||||
rotateMob(pc, (Mob) target, rotation, false);
|
||||
rotateMob(pc, (Mob) target, (float) Math.toRadians(rot));
|
||||
else
|
||||
throwbackError(pc, "Target " + target.getObjectType()
|
||||
+ " is not a valid object type");
|
||||
@@ -141,15 +140,13 @@ public class RotateCmd extends AbstractDevCmd {
|
||||
}
|
||||
}
|
||||
|
||||
Vector3f rotation = new Vector3f(0f, rot, 0f);
|
||||
|
||||
Building b;
|
||||
if (id != 0)
|
||||
b = BuildingManager.getBuilding(id);
|
||||
else
|
||||
b = getTargetAsBuilding(pc);
|
||||
if (b != null) {
|
||||
rotateBuilding(pc, b, rotation, rot, false);
|
||||
rotateBuilding(pc, b, (float) Math.toRadians(rot));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -160,7 +157,7 @@ public class RotateCmd extends AbstractDevCmd {
|
||||
else
|
||||
npc = getTargetAsNPC(pc);
|
||||
if (npc != null) {
|
||||
rotateNPC(pc, npc, rotation, false);
|
||||
rotateNPC(pc, npc, (float) Math.toRadians(rot));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,7 +168,7 @@ public class RotateCmd extends AbstractDevCmd {
|
||||
else
|
||||
mob = getTargetAsMob(pc);
|
||||
if (mob != null) {
|
||||
rotateMob(pc, mob, rotation, false);
|
||||
rotateMob(pc, mob, (float) Math.toRadians(rot));
|
||||
return;
|
||||
}
|
||||
throwbackError(pc, "Nothing found to rotate.");
|
||||
@@ -185,40 +182,52 @@ public class RotateCmd extends AbstractDevCmd {
|
||||
|
||||
@Override
|
||||
protected String _getUsageString() {
|
||||
return "' /rotate [objectID] rotation' || ' /rot [objectID] rotation'";
|
||||
return "' /rotate [objectID] degrees' || ' /rot [objectID] degrees' || ' /rotate face'";
|
||||
}
|
||||
|
||||
private void rotateBuilding(PlayerCharacter pc, Building building, Vector3f rot, float orig, boolean faceDirection) {
|
||||
if (!faceDirection)
|
||||
rot.set(0.0f, (float) Math.sin(Math.toRadians(orig) / 2), 0.0f);
|
||||
building.setRot(rot);
|
||||
building.setw((float) Math.abs(Math.cos(Math.toRadians(orig) / 2)));
|
||||
private void rotateBuilding(PlayerCharacter pc, Building building, float yaw) {
|
||||
|
||||
if (this.refuseStaticTarget(pc, building))
|
||||
return;
|
||||
|
||||
building.setRotation(Quaternion.fromYaw(yaw));
|
||||
building.getBounds().setBounds(building);
|
||||
WorldGrid.updateObject(building, pc);
|
||||
DbManager.BuildingQueries.SET_PROPERTY(building, "rotY", building.getRot().getY());
|
||||
DbManager.BuildingQueries.SET_PROPERTY(building, "w", building.getw());
|
||||
DbManager.BuildingQueries.SET_PROPERTY(building, "rotY", building.getRotation().y);
|
||||
DbManager.BuildingQueries.SET_PROPERTY(building, "w", building.getRotation().w);
|
||||
ChatManager.chatSayInfo(pc,
|
||||
"Building with ID " + building.getObjectUUID() + " rotated");
|
||||
}
|
||||
|
||||
private void rotateNPC(PlayerCharacter pc, NPC npc, Vector3f rot, boolean faceDirection) {
|
||||
npc.setRot(rot);
|
||||
DbManager.NPCQueries.SET_PROPERTY(npc, "npc_rotation", rot.y);
|
||||
private void rotateNPC(PlayerCharacter pc, NPC npc, float yaw) {
|
||||
|
||||
if (this.refuseStaticTarget(pc, npc))
|
||||
return;
|
||||
|
||||
npc.setRotation(characterRotation(yaw));
|
||||
DbManager.NPCQueries.SET_PROPERTY(npc, "npc_rotation", npc.getRotation().w);
|
||||
WorldGrid.updateObject(npc, pc);
|
||||
//no rotation for npc's in db currently
|
||||
ChatManager.chatSayInfo(pc,
|
||||
"NPC with ID " + npc.getObjectUUID() + " rotated");
|
||||
}
|
||||
|
||||
private void rotateMob(PlayerCharacter pc, Mob mob, Vector3f rot, boolean faceDirection) {
|
||||
mob.setRot(rot);
|
||||
DbManager.MobQueries.SET_PROPERTY(mob, "mob_rotation", rot.y);
|
||||
private void rotateMob(PlayerCharacter pc, Mob mob, float yaw) {
|
||||
|
||||
if (this.refuseStaticTarget(pc, mob))
|
||||
return;
|
||||
|
||||
mob.setRotation(characterRotation(yaw));
|
||||
DbManager.MobQueries.SET_PROPERTY(mob, "mob_rotation", mob.getRotation().w);
|
||||
WorldGrid.updateObject(mob, pc);
|
||||
//no rotation for mobs's in db currently
|
||||
ChatManager.chatSayInfo(pc,
|
||||
"Mob with ID " + mob.getObjectUUID() + " rotated");
|
||||
}
|
||||
|
||||
// npc_rotation and mob_rotation store w = cos θ/2, which reloads to the same yaw only for θ in [0, 2π).
|
||||
private static Quaternion characterRotation(float yaw) {
|
||||
return Quaternion.fromYaw((float) (yaw - FastMath.TWO_PI * Math.floor(yaw / FastMath.TWO_PI)));
|
||||
}
|
||||
|
||||
private void rotateFace(PlayerCharacter pc, AbstractGameObject target) {
|
||||
AbstractWorldObject awo = (AbstractWorldObject) target;
|
||||
if (awo == null)
|
||||
@@ -230,19 +239,12 @@ public class RotateCmd extends AbstractDevCmd {
|
||||
|
||||
float rotangle = faceDirection.getRotation();
|
||||
|
||||
float rot = (float) Math.toDegrees(rotangle);
|
||||
|
||||
if (rot > 180)
|
||||
rot *= -1;
|
||||
|
||||
Vector3f buildingrotation = new Vector3f(0f, rot, 0f);
|
||||
Vector3f rotation = new Vector3f(0f, rotangle, 0f);
|
||||
if (target instanceof Building)
|
||||
rotateBuilding(pc, (Building) target, buildingrotation, rot, false);
|
||||
rotateBuilding(pc, (Building) target, rotangle);
|
||||
else if (target instanceof NPC)
|
||||
rotateNPC(pc, (NPC) target, rotation, true);
|
||||
rotateNPC(pc, (NPC) target, rotangle);
|
||||
else if (target instanceof Mob)
|
||||
rotateMob(pc, (Mob) target, rotation, true);
|
||||
rotateMob(pc, (Mob) target, rotangle);
|
||||
else
|
||||
throwbackError(pc, "Target " + target.getObjectType()
|
||||
+ " is not a valid object type");
|
||||
|
||||
@@ -66,6 +66,9 @@ public class SetBuildingAltitudeCmd extends AbstractDevCmd {
|
||||
|
||||
Building targetBuilding = (Building) target;
|
||||
|
||||
if (this.refuseStaticTarget(pc, targetBuilding))
|
||||
return;
|
||||
|
||||
|
||||
float altitude = 0;
|
||||
try {
|
||||
|
||||
@@ -36,6 +36,8 @@ public class SetForceRenameCityCmd extends AbstractDevCmd {
|
||||
City city = City.getCity(zone.playerCityUUID);
|
||||
if (city == null)
|
||||
return;
|
||||
if (this.refuseStaticTarget(pc, city))
|
||||
return;
|
||||
city.setForceRename(rename);
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ public class SetGuildCmd extends AbstractDevCmd {
|
||||
targetNPC = getTargetAsNPC(pcSender);
|
||||
targetGuild = Guild.getGuild(Integer.parseInt(args[0]));
|
||||
|
||||
if (this.refuseStaticTarget(pcSender, targetNPC))
|
||||
return;
|
||||
|
||||
DbManager.NPCQueries.SET_PROPERTY(targetNPC, "npc_guildID", args[0]);
|
||||
targetNPC.setGuild(targetGuild);
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ public class SetNpcNameCmd extends AbstractDevCmd {
|
||||
|
||||
NPC npc = (NPC) target;
|
||||
|
||||
if (this.refuseStaticTarget(pc, npc))
|
||||
return;
|
||||
|
||||
String name = words[0];
|
||||
|
||||
NPC.UpdateName(npc, name);
|
||||
|
||||
@@ -35,6 +35,9 @@ public class SetOwnerCmd extends AbstractDevCmd {
|
||||
|
||||
_targetBuilding = getTargetAsBuilding(pcSender);
|
||||
|
||||
if (this.refuseStaticTarget(pcSender, _targetBuilding))
|
||||
return;
|
||||
|
||||
// if it's a tol change ownership of the city
|
||||
|
||||
if (_targetBuilding.getBlueprint() != null &&
|
||||
|
||||
@@ -51,6 +51,10 @@ public class SetRankCmd extends AbstractDevCmd {
|
||||
}
|
||||
|
||||
if (target != null) {
|
||||
|
||||
if (this.refuseStaticTarget(player, target))
|
||||
return;
|
||||
|
||||
switch (target.getObjectType()) {
|
||||
case Building:
|
||||
Building targetBuilding = (Building) target;
|
||||
@@ -99,6 +103,9 @@ public class SetRankCmd extends AbstractDevCmd {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.refuseStaticTarget(player, targetBuilding))
|
||||
return;
|
||||
|
||||
Blueprint blueprint = targetBuilding.getBlueprint();
|
||||
|
||||
if (blueprint == null) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@@ -44,6 +45,7 @@ public enum BuildingManager {
|
||||
public static HashMap<Integer, ConcurrentHashMap<Integer, BuildingFriends>> _buildingFriends = new HashMap<>();
|
||||
public static HashMap<Integer, ConcurrentHashMap<Integer, Condemned>> _buildingCondemned = new HashMap<>();
|
||||
public static HashMap<Integer, ArrayList<Vector3fImmutable>> _buildingPatrolPoints = new HashMap<>();
|
||||
public static HashSet<Integer> _npcBuildingOwners = new HashSet<>();
|
||||
|
||||
public static int getAvailableSlot(Building building) {
|
||||
|
||||
@@ -723,7 +725,7 @@ public enum BuildingManager {
|
||||
Vector3fImmutable bindLoc = null;
|
||||
|
||||
if (building == null)
|
||||
return mbEnums.Ruins.getRandomRuin().getLocation();
|
||||
return ZoneManager.getRandomRuinLocation();
|
||||
|
||||
bindLoc = building.getLoc();
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ public enum ConfigManager {
|
||||
MB_WORLD_KEYCLONE_MAX,
|
||||
MB_USE_RUINS,
|
||||
MB_RULESET,
|
||||
MB_STATIC_BUILDINGS,
|
||||
|
||||
// Mobile AI modifiers
|
||||
MB_AI_CAST_FREQUENCY,
|
||||
|
||||
@@ -23,10 +23,15 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public enum DbManager {
|
||||
DBMANAGER;
|
||||
|
||||
// Static world IDs count down from -2 (-1 means "not found"); runtime negative IDs start below them.
|
||||
public static final int RUNTIME_ID_START = -1_000_000;
|
||||
private static final AtomicInteger lastStaticID = new AtomicInteger(-1);
|
||||
|
||||
public static final dbAccountHandler AccountQueries = new dbAccountHandler();
|
||||
public static final dbBaneHandler BaneQueries = new dbBaneHandler();
|
||||
|
||||
@@ -64,12 +69,59 @@ public enum DbManager {
|
||||
public static final dbBlueprintHandler BlueprintQueries = new dbBlueprintHandler();
|
||||
public static final dbShrineHandler ShrineQueries = new dbShrineHandler();
|
||||
public static final dbRunegateHandler RunegateQueries = new dbRunegateHandler();
|
||||
public static final dbStaticWorldHandler StaticWorldQueries = new dbStaticWorldHandler();
|
||||
|
||||
public static final dbPetitionHandler PetitionQueries = new dbPetitionHandler();
|
||||
private static final EnumMap<GameObjectType, ConcurrentHashMap<Integer, AbstractGameObject>> objectCache = new EnumMap<>(GameObjectType.class);
|
||||
public static Hasher hasher;
|
||||
private static HikariDataSource connectionPool = null;
|
||||
|
||||
// Only the static world factories may call this, in the fixed load order (W2); never renumber.
|
||||
// The key names the object from the data files alone (findings §5, manifest format) and must be unique.
|
||||
public static int nextStaticID(GameObjectType objectType, String manifestKey) {
|
||||
|
||||
int staticID = lastStaticID.decrementAndGet();
|
||||
|
||||
if (staticID <= RUNTIME_ID_START)
|
||||
throw new IllegalStateException("Static world ID " + staticID + " reached the runtime ID range at " + RUNTIME_ID_START);
|
||||
|
||||
StaticManifest.addEntry(staticID, objectType, manifestKey);
|
||||
|
||||
return staticID;
|
||||
}
|
||||
|
||||
// W3: static world objects never reach the DB. Logs the refused handler method and its caller.
|
||||
public static boolean refuseStaticWrite(AbstractWorldObject worldObject) {
|
||||
|
||||
StackTraceElement[] stack;
|
||||
String caller;
|
||||
int frame = 1;
|
||||
|
||||
if (worldObject == null || !worldObject.isStaticWorldObject())
|
||||
return false;
|
||||
|
||||
stack = new Throwable().getStackTrace();
|
||||
|
||||
while (frame < stack.length - 1 && stack[frame].getClassName().equals(DbManager.class.getName()))
|
||||
frame++;
|
||||
|
||||
caller = (frame + 1 < stack.length) ? stack[frame + 1].toString() : "unknown";
|
||||
|
||||
Logger.error("Refused DB write for static " + worldObject.getObjectType() + " " + worldObject.getObjectUUID()
|
||||
+ " in " + stack[frame] + " called from " + caller);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean refuseStaticWrite(GameObjectType objectType, long objectUUID) {
|
||||
|
||||
AbstractGameObject gameObject = getFromCache(objectType, (int) objectUUID);
|
||||
|
||||
if (gameObject instanceof AbstractWorldObject)
|
||||
return refuseStaticWrite((AbstractWorldObject) gameObject);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static AbstractGameObject getObject(GameObjectType objectType, int objectUUID) {
|
||||
|
||||
AbstractGameObject outObject = null;
|
||||
|
||||
@@ -131,6 +131,11 @@ public enum ForgeManager implements Runnable {
|
||||
if (city == null)
|
||||
return 58; //58: The formula is beyond the means of this facility
|
||||
|
||||
// NPC buildings have no strongbox to pay from, as in vendor buy, repair and train
|
||||
|
||||
if (workOrder.vendor.building.getProtectionState() == mbEnums.ProtectionState.NPC)
|
||||
return 58;
|
||||
|
||||
// Concurrency is rightly managed by same lock as warehouse
|
||||
|
||||
city.transactionLock.writeLock().lock();
|
||||
|
||||
@@ -16,7 +16,18 @@ import engine.net.client.ClientConnection;
|
||||
import engine.net.client.msg.guild.AcceptInviteToGuildMsg;
|
||||
import engine.net.client.msg.guild.GuildInfoMsg;
|
||||
import engine.objects.*;
|
||||
import engine.parsers.map.GuildSeedParser;
|
||||
import engine.parsers.objects.CZoneProp;
|
||||
import engine.parsers.objects.GuildSeed;
|
||||
import engine.parsers.objects.GuildSeedEntry;
|
||||
import org.joda.time.DateTime;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public enum GuildManager {
|
||||
|
||||
@@ -33,6 +44,100 @@ public enum GuildManager {
|
||||
public static final int CREST_RESERVED = 153; //153: Guild crest is reserved
|
||||
public static final int CREST_COLOR_ERROR = 154; //154: All three crest colors cannot be the same
|
||||
|
||||
// NPC guilds by CZONE city_prop_gm_guild / city_prop_gm_nation key (W7)
|
||||
public static final HashMap<Integer, Guild> _npcGuildsByKey = new HashMap<>();
|
||||
public static final HashMap<Integer, Guild> _npcGuildsByNationKey = new HashMap<>();
|
||||
public static final HashSet<Integer> _noGuildKeys = new HashSet<>();
|
||||
public static final HashSet<Integer> _ruinKeys = new HashSet<>();
|
||||
public static final HashMap<Integer, GuildSeedEntry> _npcSeedEntriesByGuildID = new HashMap<>();
|
||||
|
||||
// Static NPC guilds from maps/<map>.guild. Loaded before zones, so they take the first static IDs. False aborts boot.
|
||||
|
||||
public static boolean loadStaticGuilds(int mapID) {
|
||||
|
||||
GuildSeed guildSeed;
|
||||
Guild guild;
|
||||
|
||||
guildSeed = GuildSeedParser.parseGuildSeed(mapID);
|
||||
|
||||
if (guildSeed == null)
|
||||
return false;
|
||||
|
||||
for (GuildSeedEntry seedEntry : guildSeed.guilds) {
|
||||
|
||||
guild = Guild.createStaticGuild(seedEntry);
|
||||
DbManager.addToCache(guild);
|
||||
_npcSeedEntriesByGuildID.put(guild.getObjectUUID(), seedEntry);
|
||||
|
||||
for (int guildKey : seedEntry.guildKeys)
|
||||
if (_npcGuildsByKey.put(guildKey, guild) != null || guildSeed.noGuildKeys.contains(guildKey) || guildSeed.ruinKeys.contains(guildKey)) {
|
||||
Logger.error("Guild seed: guild key " + String.format("%08X", guildKey) + " is used twice");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_npcGuildsByNationKey.put(seedEntry.nationKey, guild) != null) {
|
||||
Logger.error("Guild seed: nation key " + String.format("%08X", seedEntry.nationKey) + " is used twice");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_noGuildKeys.addAll(guildSeed.noGuildKeys);
|
||||
_noGuildKeys.addAll(guildSeed.ruinKeys);
|
||||
_ruinKeys.addAll(guildSeed.ruinKeys);
|
||||
|
||||
Logger.info("NPC guilds: " + guildSeed.guilds.size() + " static, " + _npcGuildsByKey.size() + " guild keys");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Logs each CITY-prop guild key on the map that has no seed entry; known no-guild keys are only counted.
|
||||
|
||||
public static void logMissingGuildKeys() {
|
||||
|
||||
TreeMap<String, TreeSet<String>> missingKeys = new TreeMap<>();
|
||||
ArrayList<CZoneProp> cityProps = new ArrayList<>();
|
||||
int noGuildProps = 0;
|
||||
|
||||
for (Zone zone : ZoneManager.getAllZones()) {
|
||||
|
||||
if (!zone.isStaticWorldObject() || zone.template == null)
|
||||
continue;
|
||||
|
||||
cityProps.clear();
|
||||
collectCityProps(zone.template.zone_prop_info, cityProps);
|
||||
|
||||
for (CZoneProp cityProp : cityProps) {
|
||||
|
||||
if (_npcGuildsByKey.containsKey(cityProp.city_prop_gm_guild))
|
||||
continue;
|
||||
|
||||
if (_noGuildKeys.contains(cityProp.city_prop_gm_guild)) {
|
||||
noGuildProps++;
|
||||
continue;
|
||||
}
|
||||
|
||||
missingKeys.computeIfAbsent(String.format("%08X", cityProp.city_prop_gm_guild), key -> new TreeSet<>())
|
||||
.add(zone.zoneName);
|
||||
}
|
||||
}
|
||||
|
||||
for (String guildKey : missingKeys.keySet())
|
||||
Logger.warn("CITY prop guild key " + guildKey + " has no guild seed entry; zones: " + missingKeys.get(guildKey));
|
||||
|
||||
Logger.info("CITY props: " + noGuildProps + " with no guild, " + missingKeys.size() + " guild keys missing from the seed");
|
||||
}
|
||||
|
||||
private static void collectCityProps(ArrayList<CZoneProp> props, ArrayList<CZoneProp> cityProps) {
|
||||
|
||||
for (CZoneProp prop : props) {
|
||||
|
||||
if (prop.prop_type == mbEnums.ZonePropType.CITY)
|
||||
cityProps.add(prop);
|
||||
|
||||
collectCityProps(prop.prop_content_props, cityProps);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean joinGuild(PlayerCharacter pc, Guild guild, GuildHistoryType historyType) {
|
||||
return joinGuild(pc, guild, 0, historyType);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public enum ItemManager {
|
||||
ITEMMANAGER;
|
||||
|
||||
public static final AtomicInteger lastNegativeID = new AtomicInteger(0);
|
||||
// Shared by every runtime object with a negative ID: loot, forged and contract items, pets and minions.
|
||||
public static final AtomicInteger lastNegativeID = new AtomicInteger(DbManager.RUNTIME_ID_START);
|
||||
|
||||
// restrict true: the set is a blacklist; false: a whitelist. An empty set allows everyone.
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ public enum MovementManager {
|
||||
|
||||
// if inside a building, convert both locations from the building local reference frame to the world reference frame
|
||||
|
||||
if (msg.getInBuildingUUID() > 0) {
|
||||
if (msg.getInBuildingUUID() != 0) {
|
||||
Building building = BuildingManager.getBuildingFromCache(msg.getInBuildingUUID());
|
||||
if (building != null) {
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ package engine.gameManager;
|
||||
import engine.InterestManagement.WorldGrid;
|
||||
import engine.loot.WorkOrder;
|
||||
import engine.math.Quaternion;
|
||||
import engine.math.Vector3f;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import engine.net.Dispatch;
|
||||
@@ -32,8 +31,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static engine.math.FastMath.acos;
|
||||
|
||||
public enum NPCManager {
|
||||
|
||||
NPC_MANAGER;
|
||||
@@ -227,11 +224,12 @@ public enum NPCManager {
|
||||
|
||||
abstractCharacter.loc = new Vector3fImmutable(abstractCharacter.bindLoc);
|
||||
|
||||
// Rotate NPC rotation by the building's rotation
|
||||
// Rotate NPC rotation by the building's rotation.
|
||||
// Kept for wire parity: the product's sin θ/2 is read back as cos θ/2, so the yaw sent is not own + building.
|
||||
|
||||
Quaternion slotRotation = new Quaternion().fromAngles(0, acos(abstractCharacter.getRot().y) * 2, 0);
|
||||
Quaternion slotRotation = new Quaternion().fromAngles(0, abstractCharacter.getRotation().getYaw(), 0);
|
||||
slotRotation = slotRotation.mult(abstractCharacter.building.getBounds().getQuaternion());
|
||||
abstractCharacter.setRot(new Vector3f(0, slotRotation.y, 0));
|
||||
abstractCharacter.setRotation(Quaternion.fromYaw((float) Math.acos(slotRotation.y) * 2));
|
||||
|
||||
// Configure region and floor/level for this NPC
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.gameManager;
|
||||
|
||||
import engine.mbEnums.GameObjectType;
|
||||
import engine.mbEnums.StaticBuildingMode;
|
||||
import engine.mbEnums.ZonePropType;
|
||||
import engine.objects.Building;
|
||||
import engine.objects.Guild;
|
||||
import engine.objects.Zone;
|
||||
import engine.parsers.objects.CZonePlacement;
|
||||
import engine.parsers.objects.CZoneProp;
|
||||
import engine.parsers.objects.ZoneTreeEntry;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
// World Boot Plan 2.4: static buildings from the CZONE props of the static zones. IDs follow the zones, zone by
|
||||
// zone in tree order, top-level props in JSON order. Each prop's children take their IDs (JSON order) before
|
||||
// the prop itself, so the parent has the lowest ID of its family, as DB UIDs did. Never renumber.
|
||||
|
||||
public enum StaticBuildingLoader {
|
||||
|
||||
STATICBUILDINGLOADER;
|
||||
|
||||
private static final float TWIN_POSITION_TOLERANCE = 0.01f;
|
||||
private static final float TWIN_YAW_TOLERANCE = 0.0001f;
|
||||
|
||||
public static StaticBuildingMode getMode() {
|
||||
return StaticBuildingMode.fromConfig(ConfigManager.MB_STATIC_BUILDINGS.getValue());
|
||||
}
|
||||
|
||||
// addToWorld false (parity) builds every building with ID 0, outside the cache and the world grid; the
|
||||
// static IDs are still taken, so the manifest matches a JSON boot.
|
||||
|
||||
public static ArrayList<StaticBuildingEntry> buildStaticBuildings(boolean addToWorld) {
|
||||
|
||||
ArrayList<StaticBuildingEntry> topLevelEntries = new ArrayList<>();
|
||||
ArrayList<CZoneProp> keptZoneProps = new ArrayList<>();
|
||||
ArrayList<CZoneProp> keptChildProps = new ArrayList<>();
|
||||
ArrayList<Integer> keptChildIndexes = new ArrayList<>();
|
||||
ArrayList<Integer> childIDs = new ArrayList<>();
|
||||
HashMap<String, Integer> leadersByName;
|
||||
StaticBuildingEntry entry;
|
||||
StaticBuildingEntry childEntry;
|
||||
Zone zone;
|
||||
CZoneProp prop;
|
||||
CZoneProp childProp;
|
||||
String key;
|
||||
int buildingCount = 0;
|
||||
int twinCount;
|
||||
int totalTwins = 0;
|
||||
int ownerMissing = 0;
|
||||
int ownerUUID;
|
||||
|
||||
leadersByName = DbManager.StaticWorldQueries.GET_NPC_GUILD_LEADERS();
|
||||
|
||||
for (ZoneTreeEntry treeEntry : ZoneManager.zoneTree) {
|
||||
|
||||
zone = ZoneManager.staticZonesByTreeUID.get(treeEntry.uid);
|
||||
keptZoneProps.clear();
|
||||
twinCount = 0;
|
||||
|
||||
for (int propIndex = 0; propIndex < zone.template.zone_prop_info.size(); propIndex++) {
|
||||
|
||||
prop = zone.template.zone_prop_info.get(propIndex);
|
||||
|
||||
// A dropped twin takes its children with it
|
||||
if (isStackedTwin(prop, keptZoneProps)) {
|
||||
twinCount += 1 + prop.prop_content_props.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
keptZoneProps.add(prop);
|
||||
key = "prop:" + treeEntry.uid + "/" + propIndex;
|
||||
keptChildProps.clear();
|
||||
keptChildIndexes.clear();
|
||||
childIDs.clear();
|
||||
|
||||
for (int childIndex = 0; childIndex < prop.prop_content_props.size(); childIndex++) {
|
||||
|
||||
childProp = prop.prop_content_props.get(childIndex);
|
||||
|
||||
if (isStackedTwin(childProp, keptChildProps)) {
|
||||
twinCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
keptChildProps.add(childProp);
|
||||
keptChildIndexes.add(childIndex);
|
||||
}
|
||||
|
||||
for (int childIndex : keptChildIndexes)
|
||||
childIDs.add(DbManager.nextStaticID(GameObjectType.Building, key + "/" + childIndex));
|
||||
|
||||
ownerUUID = getOwnerUUID(prop, leadersByName);
|
||||
|
||||
if (prop.prop_type == ZonePropType.CITY && ownerUUID == 0 && GuildManager._npcGuildsByKey.containsKey(prop.city_prop_gm_guild))
|
||||
ownerMissing++;
|
||||
|
||||
entry = buildEntry(treeEntry.uid, key, DbManager.nextStaticID(GameObjectType.Building, key), zone, prop, null, ownerUUID, addToWorld);
|
||||
topLevelEntries.add(entry);
|
||||
buildingCount++;
|
||||
|
||||
for (int i = 0; i < keptChildProps.size(); i++) {
|
||||
childEntry = buildEntry(treeEntry.uid, key + "/" + keptChildIndexes.get(i), childIDs.get(i), zone, keptChildProps.get(i), entry, 0, addToWorld);
|
||||
entry.children.add(childEntry);
|
||||
buildingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (twinCount > 0)
|
||||
Logger.info("Static buildings: " + twinCount + " stacked twins dropped in zone " + treeEntry.uid + " (" + treeEntry.name + ")");
|
||||
|
||||
totalTwins += twinCount;
|
||||
}
|
||||
|
||||
if (ownerMissing > 0)
|
||||
Logger.warn("Static buildings: " + ownerMissing + " CITY props name a guild with no DB leader NPC; left unowned");
|
||||
|
||||
Logger.info("Static buildings: " + buildingCount + " built, " + totalTwins + " stacked twins dropped");
|
||||
|
||||
return topLevelEntries;
|
||||
}
|
||||
|
||||
private static StaticBuildingEntry buildEntry(int zoneTreeUID, String key, int staticID, Zone zone, CZoneProp prop, StaticBuildingEntry parentEntry,
|
||||
int ownerUUID, boolean addToWorld) {
|
||||
|
||||
StaticBuildingEntry entry = new StaticBuildingEntry();
|
||||
|
||||
entry.key = key;
|
||||
entry.zoneTreeUID = zoneTreeUID;
|
||||
entry.prop = prop;
|
||||
entry.parent = parentEntry;
|
||||
entry.staticID = staticID;
|
||||
entry.building = Building.createStaticBuilding(addToWorld ? entry.staticID : 0, zone, prop,
|
||||
parentEntry == null ? null : parentEntry.building, ownerUUID);
|
||||
|
||||
if (addToWorld) {
|
||||
DbManager.addToCache(entry.building);
|
||||
entry.building.runAfterLoad();
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Same mesh, name, local position and yaw as an earlier kept sibling (findings §6, stacked duplicates;
|
||||
// the name keeps Swampstone's stacked vendor halls).
|
||||
private static boolean isStackedTwin(CZoneProp prop, ArrayList<CZoneProp> keptSiblings) {
|
||||
|
||||
CZonePlacement placement = prop.prop_basic_zone;
|
||||
CZonePlacement keptPlacement;
|
||||
|
||||
for (CZoneProp kept : keptSiblings) {
|
||||
|
||||
keptPlacement = kept.prop_basic_zone;
|
||||
|
||||
if (keptPlacement.basic_zone_template_id != placement.basic_zone_template_id
|
||||
|| !keptPlacement.basic_zone_name_override.equals(placement.basic_zone_name_override))
|
||||
continue;
|
||||
|
||||
if (Math.abs(keptPlacement.basic_zone_spawn_location.x - placement.basic_zone_spawn_location.x) <= TWIN_POSITION_TOLERANCE
|
||||
&& Math.abs(keptPlacement.basic_zone_spawn_location.y - placement.basic_zone_spawn_location.y) <= TWIN_POSITION_TOLERANCE
|
||||
&& Math.abs(keptPlacement.basic_zone_spawn_location.z - placement.basic_zone_spawn_location.z) <= TWIN_POSITION_TOLERANCE
|
||||
&& Math.abs(keptPlacement.basic_zone_y_rot - placement.basic_zone_y_rot) <= TWIN_YAW_TOLERANCE)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// The guild the CITY key names, owned through its DB leader NPC as before the switch.
|
||||
private static int getOwnerUUID(CZoneProp prop, HashMap<String, Integer> leadersByName) {
|
||||
|
||||
Guild guild;
|
||||
Integer leaderUUID;
|
||||
|
||||
if (prop.prop_type != ZonePropType.CITY)
|
||||
return 0;
|
||||
|
||||
guild = GuildManager._npcGuildsByKey.get(prop.city_prop_gm_guild);
|
||||
|
||||
if (guild == null)
|
||||
return 0;
|
||||
|
||||
leaderUUID = leadersByName.get(guild.getName());
|
||||
|
||||
return leaderUUID == null ? 0 : leaderUUID;
|
||||
}
|
||||
|
||||
public static class StaticBuildingEntry {
|
||||
|
||||
public String key;
|
||||
public int zoneTreeUID;
|
||||
public int staticID;
|
||||
public CZoneProp prop;
|
||||
public Building building;
|
||||
public StaticBuildingEntry parent;
|
||||
public final ArrayList<StaticBuildingEntry> children = new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.gameManager;
|
||||
|
||||
import engine.gameManager.StaticBuildingLoader.StaticBuildingEntry;
|
||||
import engine.math.Quaternion;
|
||||
import engine.mbEnums.ZonePropType;
|
||||
import engine.objects.Building;
|
||||
import engine.objects.Guild;
|
||||
import engine.objects.Zone;
|
||||
import engine.parsers.objects.CZoneProp;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
// World Boot Plan 2.3/2.4: builds the static buildings from the CZONE props with the IDs a JSON boot gives them,
|
||||
// compares them with the DB buildings they replace, and writes maps/<map>.migration.sql, the old UID -> static ID
|
||||
// mapping the 2.4 migration script re-points references with. The JSON buildings are discarded.
|
||||
|
||||
public enum StaticBuildingParity {
|
||||
|
||||
STATICBUILDINGPARITY;
|
||||
|
||||
private static final String mapPath = ConfigManager.DEFAULT_DATA_DIR + "maps/";
|
||||
private static final float POSITION_TOLERANCE = 0.01f;
|
||||
private static final float ROTATION_TOLERANCE = 0.0001f;
|
||||
private static final int EXAMPLES = 5;
|
||||
private static final int INSERT_BATCH = 500;
|
||||
private static final String DB_ONLY = "DB only (unmapped, deleted by the migration)";
|
||||
|
||||
private static final TreeMap<String, ArrayList<String>> differences = new TreeMap<>();
|
||||
private static final HashMap<Integer, StaticBuildingEntry> entriesByDbUID = new HashMap<>();
|
||||
private static final HashSet<StaticBuildingEntry> matchedEntries = new HashSet<>();
|
||||
private static final HashMap<Integer, ArrayList<Building>> dbChildrenByParent = new HashMap<>();
|
||||
private static int propCount;
|
||||
private static int exactCount;
|
||||
private static int movedCount;
|
||||
private static int twinCount;
|
||||
private static float maxChildMove;
|
||||
|
||||
// Run after NPCs load, so a DB building's owner resolves to its guild.
|
||||
public static void run(int mapID) {
|
||||
|
||||
ArrayList<StaticBuildingEntry> topLevelEntries;
|
||||
HashMap<Integer, ArrayList<StaticBuildingEntry>> entriesByZone = new HashMap<>();
|
||||
HashMap<Integer, ArrayList<Building>> dbTopLevelByZone = new HashMap<>();
|
||||
ArrayList<Building> dbStatic = new ArrayList<>();
|
||||
ArrayList<Building> dbUnmatchedTop = new ArrayList<>();
|
||||
Building dbBuilding;
|
||||
|
||||
topLevelEntries = StaticBuildingLoader.buildStaticBuildings(false);
|
||||
|
||||
for (StaticBuildingEntry entry : topLevelEntries) {
|
||||
entriesByZone.computeIfAbsent(entry.zoneTreeUID, k -> new ArrayList<>()).add(entry);
|
||||
propCount += 1 + entry.children.size();
|
||||
}
|
||||
|
||||
// Mine towers (blueprint 1500100) stay DB objects (Task 2.1, D1); every CZONE prop has blueprint 0.
|
||||
for (Zone zone : ZoneManager.staticZonesByTreeUID.values())
|
||||
for (Building building : zone.zoneBuildingSet) {
|
||||
|
||||
if (building.blueprintUUID != 0)
|
||||
continue;
|
||||
|
||||
dbStatic.add(building);
|
||||
|
||||
if (building.parentBuildingID == 0)
|
||||
dbTopLevelByZone.computeIfAbsent(zone.getObjectUUID(), k -> new ArrayList<>()).add(building);
|
||||
else
|
||||
dbChildrenByParent.computeIfAbsent(building.parentBuildingID, k -> new ArrayList<>()).add(building);
|
||||
}
|
||||
|
||||
// Pass 1: zone, mesh and local x/z.
|
||||
for (Map.Entry<Integer, ArrayList<StaticBuildingEntry>> zoneEntries : entriesByZone.entrySet())
|
||||
for (StaticBuildingEntry entry : zoneEntries.getValue()) {
|
||||
|
||||
dbBuilding = takeMatch(entry, dbTopLevelByZone.getOrDefault(zoneEntries.getKey(), new ArrayList<>()));
|
||||
|
||||
if (dbBuilding == null)
|
||||
continue;
|
||||
|
||||
exactCount++;
|
||||
mapPair(dbBuilding, entry, true);
|
||||
}
|
||||
|
||||
for (ArrayList<Building> leftovers : dbTopLevelByZone.values())
|
||||
dbUnmatchedTop.addAll(leftovers);
|
||||
|
||||
// Pass 2: hand-moved DB rows (Swampstone halls, Silverlance shop).
|
||||
for (Building building : new ArrayList<>(dbUnmatchedTop)) {
|
||||
|
||||
StaticBuildingEntry entry = findMovedMatch(building, entriesByZone);
|
||||
|
||||
if (entry == null)
|
||||
continue;
|
||||
|
||||
dbUnmatchedTop.remove(building);
|
||||
movedCount++;
|
||||
mapPair(building, entry, true);
|
||||
}
|
||||
|
||||
// Pass 3: a DB row of a dropped stacked twin maps to the kept twin.
|
||||
for (Building building : new ArrayList<>(dbUnmatchedTop)) {
|
||||
|
||||
StaticBuildingEntry entry = findTwin(building, entriesByZone.getOrDefault(building.parentZoneUUID, new ArrayList<>()));
|
||||
|
||||
if (entry == null)
|
||||
continue;
|
||||
|
||||
dbUnmatchedTop.remove(building);
|
||||
twinCount++;
|
||||
mapPair(building, entry, false);
|
||||
}
|
||||
|
||||
logResults(dbStatic, topLevelEntries);
|
||||
writeMigration(mapID, dbStatic);
|
||||
|
||||
differences.clear();
|
||||
entriesByDbUID.clear();
|
||||
matchedEntries.clear();
|
||||
dbChildrenByParent.clear();
|
||||
}
|
||||
|
||||
// Maps a DB top-level row and its children. compare is false for twins: their differences are the kept twin's.
|
||||
private static void mapPair(Building dbBuilding, StaticBuildingEntry entry, boolean compare) {
|
||||
|
||||
ArrayList<Building> dbChildren = dbChildrenByParent.getOrDefault(dbBuilding.getObjectUUID(), new ArrayList<>());
|
||||
ArrayList<Building> dbChildCandidates = new ArrayList<>(dbChildren);
|
||||
Building dbChild;
|
||||
StaticBuildingEntry twin;
|
||||
float childMove;
|
||||
|
||||
entriesByDbUID.put(dbBuilding.getObjectUUID(), entry);
|
||||
|
||||
if (compare) {
|
||||
matchedEntries.add(entry);
|
||||
compareBuilding(entry, dbBuilding);
|
||||
}
|
||||
|
||||
for (StaticBuildingEntry childEntry : entry.children) {
|
||||
|
||||
dbChild = takeMatch(childEntry, dbChildCandidates);
|
||||
|
||||
if (dbChild == null)
|
||||
continue;
|
||||
|
||||
entriesByDbUID.put(dbChild.getObjectUUID(), childEntry);
|
||||
|
||||
if (!compare)
|
||||
continue;
|
||||
|
||||
matchedEntries.add(childEntry);
|
||||
compareBuilding(childEntry, dbChild);
|
||||
|
||||
if (Math.abs(dbChild.getStatAlt() - childEntry.building.getStatAlt()) > POSITION_TOLERANCE)
|
||||
addDifference("child local Y", childEntry.key + " DB " + dbChild.getStatAlt() + " JSON " + childEntry.building.getStatAlt());
|
||||
|
||||
// Today's runAfterLoad adds the child offset without the parent's yaw.
|
||||
childMove = dbChild.getLoc().distance2D(childEntry.building.getLoc());
|
||||
|
||||
if (childMove > POSITION_TOLERANCE) {
|
||||
addDifference("child world x/z (parent yaw)", childEntry.key + " moved " + childMove);
|
||||
maxChildMove = Math.max(maxChildMove, childMove);
|
||||
}
|
||||
}
|
||||
|
||||
// DB children of dropped twin children map to the kept child.
|
||||
for (Building leftover : dbChildCandidates) {
|
||||
|
||||
twin = findTwin(leftover, entry.children);
|
||||
|
||||
if (twin != null)
|
||||
entriesByDbUID.put(leftover.getObjectUUID(), twin);
|
||||
}
|
||||
}
|
||||
|
||||
// Same mesh and local x/z; removes and returns the closest DB building, or null.
|
||||
private static Building takeMatch(StaticBuildingEntry entry, ArrayList<Building> candidates) {
|
||||
|
||||
Building found = null;
|
||||
float foundDistance = Float.MAX_VALUE;
|
||||
float distance;
|
||||
|
||||
for (Building candidate : candidates) {
|
||||
|
||||
distance = localDistance(candidate, entry);
|
||||
|
||||
if (distance <= POSITION_TOLERANCE && distance < foundDistance) {
|
||||
found = candidate;
|
||||
foundDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (found != null)
|
||||
candidates.remove(found);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
// An unmatched JSON prop with the same mesh in the DB row's zone or its parent zone: same name first,
|
||||
// otherwise the only candidate.
|
||||
private static StaticBuildingEntry findMovedMatch(Building dbBuilding, HashMap<Integer, ArrayList<StaticBuildingEntry>> entriesByZone) {
|
||||
|
||||
ArrayList<StaticBuildingEntry> candidates = new ArrayList<>();
|
||||
Zone zone = ZoneManager.getZoneByUUID(dbBuilding.parentZoneUUID);
|
||||
|
||||
addMovedCandidates(dbBuilding, entriesByZone.get(zone.getObjectUUID()), candidates);
|
||||
|
||||
if (zone.parent != null && zone.parent.isStaticWorldObject())
|
||||
addMovedCandidates(dbBuilding, entriesByZone.get(zone.parent.getObjectUUID()), candidates);
|
||||
|
||||
for (StaticBuildingEntry candidate : candidates)
|
||||
if (candidate.building.getName().equals(dbBuilding.getName()))
|
||||
return candidate;
|
||||
|
||||
return candidates.size() == 1 ? candidates.get(0) : null;
|
||||
}
|
||||
|
||||
private static void addMovedCandidates(Building dbBuilding, ArrayList<StaticBuildingEntry> zoneEntries, ArrayList<StaticBuildingEntry> candidates) {
|
||||
|
||||
if (zoneEntries == null)
|
||||
return;
|
||||
|
||||
for (StaticBuildingEntry entry : zoneEntries)
|
||||
if (!matchedEntries.contains(entry) && entry.building.meshUUID == dbBuilding.meshUUID)
|
||||
candidates.add(entry);
|
||||
}
|
||||
|
||||
// Any kept entry, matched or not, with the same mesh and local x/z.
|
||||
private static StaticBuildingEntry findTwin(Building dbBuilding, ArrayList<StaticBuildingEntry> siblings) {
|
||||
|
||||
for (StaticBuildingEntry entry : siblings)
|
||||
if (localDistance(dbBuilding, entry) <= POSITION_TOLERANCE)
|
||||
return entry;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Chebyshev distance on local x/z for the same mesh; MAX_VALUE for a different mesh.
|
||||
private static float localDistance(Building dbBuilding, StaticBuildingEntry entry) {
|
||||
|
||||
if (dbBuilding.meshUUID != entry.building.meshUUID)
|
||||
return Float.MAX_VALUE;
|
||||
|
||||
return Math.max(Math.abs(dbBuilding.statLat - entry.building.statLat), Math.abs(dbBuilding.statLon - entry.building.statLon));
|
||||
}
|
||||
|
||||
private static void logResults(ArrayList<Building> dbStatic, ArrayList<StaticBuildingEntry> topLevelEntries) {
|
||||
|
||||
for (Building building : dbStatic)
|
||||
if (!entriesByDbUID.containsKey(building.getObjectUUID()))
|
||||
addDifference(DB_ONLY, building.getObjectUUID() + " zone " + building.parentZoneUUID + " mesh " + building.meshUUID
|
||||
+ " '" + building.getName() + "' at " + building.statLat + ", " + building.statLon + " parent " + building.parentBuildingID);
|
||||
|
||||
for (StaticBuildingEntry entry : topLevelEntries) {
|
||||
|
||||
if (!matchedEntries.contains(entry))
|
||||
addDifference("JSON only", entry.key + " mesh " + entry.building.meshUUID + " '" + entry.building.getName() + "'");
|
||||
|
||||
for (StaticBuildingEntry childEntry : entry.children)
|
||||
if (!matchedEntries.contains(childEntry))
|
||||
addDifference("JSON only", childEntry.key + " mesh " + childEntry.building.meshUUID + " '" + childEntry.building.getName() + "'");
|
||||
}
|
||||
|
||||
Logger.info("Static building parity: " + propCount + " props, " + dbStatic.size() + " DB rows, " + entriesByDbUID.size()
|
||||
+ " mapped (" + exactCount + " top-level exact, " + movedCount + " moved, " + twinCount + " twins, rest children)"
|
||||
+ ", max child x/z move from the parent yaw " + maxChildMove);
|
||||
|
||||
// Every unmapped DB row is listed in full: the migration deletes it, so its references must be checked.
|
||||
for (Map.Entry<String, ArrayList<String>> entry : differences.entrySet()) {
|
||||
|
||||
Logger.info(" " + entry.getKey() + ": " + entry.getValue().size());
|
||||
|
||||
for (int i = 0; i < entry.getValue().size(); i++)
|
||||
if (i < EXAMPLES || entry.getKey().equals(DB_ONLY))
|
||||
Logger.info(" " + entry.getValue().get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// task24_static_map: every static zone (tree UID -> static ID) and every DB static building (UID -> static ID,
|
||||
// or NULL when unmapped). sql/task24_static_switch.sql reads it.
|
||||
private static void writeMigration(int mapID, ArrayList<Building> dbStatic) {
|
||||
|
||||
File migrationFile = new File(mapPath + mapID + ".migration.sql");
|
||||
ArrayList<String> rows = new ArrayList<>();
|
||||
StringBuilder sql = new StringBuilder();
|
||||
StaticBuildingEntry entry;
|
||||
|
||||
for (Map.Entry<Integer, Integer> zoneID : new TreeMap<>(ZoneManager.staticIDsByTreeUID).entrySet())
|
||||
rows.add("(" + zoneID.getKey() + ", " + zoneID.getValue() + ", 'zone', 'zone:" + zoneID.getKey() + "')");
|
||||
|
||||
for (Building building : dbStatic) {
|
||||
|
||||
entry = entriesByDbUID.get(building.getObjectUUID());
|
||||
|
||||
if (entry == null)
|
||||
rows.add("(" + building.getObjectUUID() + ", NULL, 'building', NULL)");
|
||||
else
|
||||
rows.add("(" + building.getObjectUUID() + ", " + entry.staticID + ", 'building', '" + entry.key + "')");
|
||||
}
|
||||
|
||||
sql.append("-- World Boot Plan 2.4: old UID -> static ID for map ").append(mapID).append(", written by MB_STATIC_BUILDINGS=parity.\n");
|
||||
sql.append("DROP TABLE IF EXISTS `task24_static_map`;\n");
|
||||
sql.append("CREATE TABLE `task24_static_map` (`oldUID` BIGINT UNSIGNED NOT NULL, `newID` INT NULL, ")
|
||||
.append("`kind` ENUM('zone','building') NOT NULL, `staticKey` VARCHAR(64) NULL, PRIMARY KEY (`oldUID`));\n");
|
||||
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
|
||||
if (i % INSERT_BATCH == 0)
|
||||
sql.append("INSERT INTO `task24_static_map` VALUES\n");
|
||||
|
||||
sql.append(rows.get(i));
|
||||
sql.append(i % INSERT_BATCH == INSERT_BATCH - 1 || i == rows.size() - 1 ? ";\n" : ",\n");
|
||||
}
|
||||
|
||||
try {
|
||||
Files.write(migrationFile.toPath(), sql.toString().getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
Logger.error("Static migration mapping not written: " + migrationFile.getAbsolutePath() + ": " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info("Static migration mapping: " + rows.size() + " rows written to " + migrationFile.getName());
|
||||
}
|
||||
|
||||
private static void compareBuilding(StaticBuildingEntry entry, Building dbBuilding) {
|
||||
|
||||
Building jsonBuilding = entry.building;
|
||||
CZoneProp prop = entry.prop;
|
||||
String prefix = entry.key + " (DB " + dbBuilding.getObjectUUID() + ")";
|
||||
String dbGuild = guildName(dbBuilding);
|
||||
String jsonGuild = "";
|
||||
|
||||
if (!sameRotation(dbBuilding.getRotation(), jsonBuilding.getRotation()))
|
||||
addDifference("rotation", prefix + " DB " + rotationText(dbBuilding.getRotation()) + " JSON " + rotationText(jsonBuilding.getRotation()));
|
||||
|
||||
if (!dbBuilding.getMeshScale().equals(jsonBuilding.getMeshScale()))
|
||||
addDifference("scale", prefix + " DB " + dbBuilding.getMeshScale() + " JSON " + jsonBuilding.getMeshScale());
|
||||
|
||||
if (dbBuilding.rank != jsonBuilding.rank)
|
||||
addDifference("rank", prefix + " DB " + dbBuilding.rank + " JSON " + jsonBuilding.rank);
|
||||
|
||||
if (dbBuilding.getStrongboxValue() != jsonBuilding.getStrongboxValue())
|
||||
addDifference("strongbox", prefix + " DB " + dbBuilding.getStrongboxValue() + " JSON " + jsonBuilding.getStrongboxValue());
|
||||
|
||||
if (!dbBuilding.getName().equals(jsonBuilding.getName()))
|
||||
addDifference("name", prefix + " DB '" + dbBuilding.getName() + "' JSON '" + jsonBuilding.getName() + "'");
|
||||
|
||||
if (dbBuilding.floor != jsonBuilding.floor || dbBuilding.level != jsonBuilding.level)
|
||||
addDifference("floor/level", prefix + " DB " + dbBuilding.floor + "/" + dbBuilding.level + " JSON " + jsonBuilding.floor + "/" + jsonBuilding.level);
|
||||
|
||||
if (dbBuilding.protectionState != jsonBuilding.protectionState)
|
||||
addDifference("protection", prefix + " DB " + dbBuilding.protectionState + " JSON " + jsonBuilding.protectionState);
|
||||
|
||||
if (dbBuilding.isFurniture != jsonBuilding.isFurniture)
|
||||
addDifference("furniture flag", prefix + " DB " + dbBuilding.isFurniture + " JSON " + jsonBuilding.isFurniture);
|
||||
|
||||
if (dbBuilding.getOwnerUUID() != jsonBuilding.getOwnerUUID())
|
||||
addDifference("owner NPC", prefix + " DB " + dbBuilding.getOwnerUUID() + " JSON " + jsonBuilding.getOwnerUUID());
|
||||
|
||||
if (prop.prop_type == ZonePropType.CITY && GuildManager._npcGuildsByKey.containsKey(prop.city_prop_gm_guild))
|
||||
jsonGuild = GuildManager._npcGuildsByKey.get(prop.city_prop_gm_guild).getName();
|
||||
|
||||
if (!dbGuild.equals(jsonGuild))
|
||||
addDifference("owner guild", prefix + " DB '" + dbGuild + "' JSON '" + jsonGuild + "'");
|
||||
}
|
||||
|
||||
// q and -q are the same rotation.
|
||||
private static boolean sameRotation(Quaternion a, Quaternion b) {
|
||||
|
||||
boolean same = Math.abs(a.w - b.w) < ROTATION_TOLERANCE && Math.abs(a.x - b.x) < ROTATION_TOLERANCE
|
||||
&& Math.abs(a.y - b.y) < ROTATION_TOLERANCE && Math.abs(a.z - b.z) < ROTATION_TOLERANCE;
|
||||
boolean negated = Math.abs(a.w + b.w) < ROTATION_TOLERANCE && Math.abs(a.x + b.x) < ROTATION_TOLERANCE
|
||||
&& Math.abs(a.y + b.y) < ROTATION_TOLERANCE && Math.abs(a.z + b.z) < ROTATION_TOLERANCE;
|
||||
|
||||
return same || negated;
|
||||
}
|
||||
|
||||
private static String rotationText(Quaternion rotation) {
|
||||
return "(w " + rotation.w + ", x " + rotation.x + ", y " + rotation.y + ", z " + rotation.z + ")";
|
||||
}
|
||||
|
||||
private static String guildName(Building building) {
|
||||
|
||||
Guild guild;
|
||||
|
||||
if (building.getOwnerUUID() == 0)
|
||||
return "";
|
||||
|
||||
guild = building.getGuild();
|
||||
|
||||
return guild == null || guild.isEmptyGuild() ? "" : guild.getName();
|
||||
}
|
||||
|
||||
private static void addDifference(String differenceClass, String line) {
|
||||
differences.computeIfAbsent(differenceClass, k -> new ArrayList<>()).add(line);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.gameManager;
|
||||
|
||||
import engine.gameManager.StaticBuildingLoader.StaticBuildingEntry;
|
||||
import engine.mbEnums.GameObjectType;
|
||||
import engine.mbEnums.ZonePropType;
|
||||
import engine.objects.City;
|
||||
import engine.objects.Guild;
|
||||
import engine.objects.Zone;
|
||||
import engine.parsers.objects.GuildSeedEntry;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
// World Boot Plan 2.5: one NPC city per seed guild, in the zone of the guild's first CITY prop (tree order, then JSON
|
||||
// order); that prop is the tree of life. Later zones with the same guild (Junktown under Swampstone) are not cities.
|
||||
// City IDs follow all static buildings. Never renumber.
|
||||
|
||||
public enum StaticCityLoader {
|
||||
|
||||
STATICCITYLOADER;
|
||||
|
||||
// addToWorld false (parity) keeps the cities out of the cache and the guilds; the static IDs are still taken.
|
||||
public static ArrayList<StaticCityEntry> buildStaticCities(ArrayList<StaticBuildingEntry> topLevelEntries, boolean addToWorld) {
|
||||
|
||||
ArrayList<StaticCityEntry> cityEntries = new ArrayList<>();
|
||||
HashSet<Guild> guildsWithCity = new HashSet<>();
|
||||
StaticCityEntry cityEntry;
|
||||
GuildSeedEntry seedEntry;
|
||||
Guild guild;
|
||||
Zone zone;
|
||||
|
||||
for (StaticBuildingEntry buildingEntry : topLevelEntries) {
|
||||
|
||||
if (buildingEntry.prop.prop_type != ZonePropType.CITY)
|
||||
continue;
|
||||
|
||||
guild = GuildManager._npcGuildsByKey.get(buildingEntry.prop.city_prop_gm_guild);
|
||||
|
||||
if (guild == null || !guildsWithCity.add(guild))
|
||||
continue;
|
||||
|
||||
zone = ZoneManager.staticZonesByTreeUID.get(buildingEntry.zoneTreeUID);
|
||||
seedEntry = GuildManager._npcSeedEntriesByGuildID.get(guild.getObjectUUID());
|
||||
|
||||
cityEntry = new StaticCityEntry();
|
||||
cityEntry.key = "city:" + buildingEntry.key.substring("prop:".length());
|
||||
cityEntry.staticID = DbManager.nextStaticID(GameObjectType.City, cityEntry.key);
|
||||
cityEntry.guild = guild;
|
||||
cityEntry.treeEntry = buildingEntry;
|
||||
cityEntry.city = City.createStaticCity(addToWorld ? cityEntry.staticID : 0, zone, buildingEntry.building, seedEntry.noobIsle);
|
||||
cityEntries.add(cityEntry);
|
||||
|
||||
if (addToWorld) {
|
||||
DbManager.addToCache(cityEntry.city);
|
||||
guild.setCityUUID(cityEntry.staticID);
|
||||
cityEntry.city.runAfterLoad();
|
||||
}
|
||||
}
|
||||
|
||||
if (guildsWithCity.size() < GuildManager._npcSeedEntriesByGuildID.size())
|
||||
Logger.warn("Static cities: " + (GuildManager._npcSeedEntriesByGuildID.size() - guildsWithCity.size())
|
||||
+ " seed guilds have no CITY prop on this map");
|
||||
|
||||
Logger.info("Static cities: " + cityEntries.size() + " built");
|
||||
|
||||
return cityEntries;
|
||||
}
|
||||
|
||||
public static class StaticCityEntry {
|
||||
|
||||
public String key;
|
||||
public int staticID;
|
||||
public Guild guild;
|
||||
public StaticBuildingEntry treeEntry;
|
||||
public City city;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.gameManager;
|
||||
|
||||
import engine.gameManager.StaticCityLoader.StaticCityEntry;
|
||||
import engine.mbEnums.GameObjectType;
|
||||
import engine.objects.AbstractGameObject;
|
||||
import engine.objects.Building;
|
||||
import engine.objects.City;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
// World Boot Plan 2.5: while obj_city still holds the NPC cities, compares them with the JSON cities (matched by
|
||||
// parent zone) and writes maps/<map>.city_migration.sql, the old UID -> static ID mapping that
|
||||
// sql/task25_static_cities.sql re-points obj_guild.ownedCity with. The JSON cities are discarded.
|
||||
|
||||
public enum StaticCityParity {
|
||||
|
||||
STATICCITYPARITY;
|
||||
|
||||
private static final String mapPath = ConfigManager.DEFAULT_DATA_DIR + "maps/";
|
||||
|
||||
// Run after NPCs load, so a tree's owner resolves to its guild.
|
||||
public static void run(int mapID, ArrayList<StaticCityEntry> cityEntries) {
|
||||
|
||||
HashMap<Integer, StaticCityEntry> entriesByZone = new HashMap<>();
|
||||
HashMap<Integer, StaticCityEntry> entriesByDbUID = new HashMap<>();
|
||||
ArrayList<City> dbCities = new ArrayList<>();
|
||||
ArrayList<String> differences = new ArrayList<>();
|
||||
StaticCityEntry entry;
|
||||
City dbCity;
|
||||
int jsonOnly = 0;
|
||||
|
||||
for (StaticCityEntry cityEntry : cityEntries)
|
||||
entriesByZone.put(cityEntry.city.parentZoneUUID, cityEntry);
|
||||
|
||||
for (AbstractGameObject gameObject : DbManager.getMap(GameObjectType.City).values()) {
|
||||
|
||||
dbCity = (City) gameObject;
|
||||
|
||||
if (dbCity.isNpc())
|
||||
dbCities.add(dbCity);
|
||||
}
|
||||
|
||||
for (City city : dbCities) {
|
||||
|
||||
entry = entriesByZone.get(city.parentZoneUUID);
|
||||
|
||||
if (entry == null) {
|
||||
differences.add("DB only: " + city.getObjectUUID() + " " + city.getCityName() + " zone " + city.parentZoneUUID);
|
||||
continue;
|
||||
}
|
||||
|
||||
entriesByDbUID.put(city.getObjectUUID(), entry);
|
||||
compareCity(city, entry, differences);
|
||||
}
|
||||
|
||||
for (StaticCityEntry cityEntry : cityEntries)
|
||||
if (!entriesByDbUID.containsValue(cityEntry)) {
|
||||
differences.add("JSON only: " + cityEntry.key + " " + cityEntry.city.getCityName());
|
||||
jsonOnly++;
|
||||
}
|
||||
|
||||
Logger.info("Static city parity: " + dbCities.size() + " DB NPC cities, " + cityEntries.size() + " JSON cities, "
|
||||
+ entriesByDbUID.size() + " matched, " + jsonOnly + " JSON only, " + differences.size() + " differences");
|
||||
|
||||
for (String difference : differences)
|
||||
Logger.info(" " + difference);
|
||||
|
||||
writeMigration(mapID, dbCities, entriesByDbUID);
|
||||
}
|
||||
|
||||
private static void compareCity(City dbCity, StaticCityEntry entry, ArrayList<String> differences) {
|
||||
|
||||
City jsonCity = entry.city;
|
||||
Building dbTree = dbCity.getTOL();
|
||||
String label = dbCity.getObjectUUID() + " " + dbCity.getCityName() + " (" + entry.key + ")";
|
||||
|
||||
compare(differences, label, "name", dbCity.getCityName(), jsonCity.getCityName());
|
||||
compare(differences, label, "tree", describeTree(dbTree), describeTree(jsonCity.getTOL()));
|
||||
compare(differences, label, "rank", dbCity.getRank(), jsonCity.getRank());
|
||||
compare(differences, label, "guild", dbCity.getGuild().getName(), entry.guild.getName());
|
||||
compare(differences, label, "noob isle", dbCity.isNoobIsle, jsonCity.isNoobIsle);
|
||||
compare(differences, label, "safehold", dbCity.isSafeHold(), jsonCity.isSafeHold());
|
||||
compare(differences, label, "established", dbCity.established, jsonCity.established);
|
||||
compare(differences, label, "sieges withstood", dbCity.getSiegesWithstood(), jsonCity.getSiegesWithstood());
|
||||
compare(differences, label, "open", dbCity.isOpen(), jsonCity.isOpen());
|
||||
compare(differences, label, "force rename", dbCity.isForceRename(), jsonCity.isForceRename());
|
||||
compare(differences, label, "bind x/z", dbCity.getBindX() + "/" + dbCity.getBindZ(), jsonCity.getBindX() + "/" + jsonCity.getBindZ());
|
||||
compare(differences, label, "bind radius", dbCity.getBindRadius() + " type " + dbCity.getRadiusType(),
|
||||
jsonCity.getBindRadius() + " type " + jsonCity.getRadiusType());
|
||||
}
|
||||
|
||||
private static String describeTree(Building tree) {
|
||||
|
||||
if (tree == null)
|
||||
return "none";
|
||||
|
||||
return tree.getObjectUUID() + " mesh " + tree.meshUUID;
|
||||
}
|
||||
|
||||
private static void compare(ArrayList<String> differences, String label, String field, Object dbValue, Object jsonValue) {
|
||||
|
||||
if (!Objects.equals(dbValue, jsonValue))
|
||||
differences.add(label + " " + field + ": DB " + dbValue + ", JSON " + jsonValue);
|
||||
}
|
||||
|
||||
// task25_city_map: every DB NPC city (UID -> static ID, or NULL when unmatched).
|
||||
private static void writeMigration(int mapID, ArrayList<City> dbCities, HashMap<Integer, StaticCityEntry> entriesByDbUID) {
|
||||
|
||||
File migrationFile = new File(mapPath + mapID + ".city_migration.sql");
|
||||
StringBuilder sql = new StringBuilder();
|
||||
StaticCityEntry entry;
|
||||
|
||||
sql.append("-- World Boot Plan 2.5: old NPC city UID -> static ID for map ").append(mapID)
|
||||
.append(", written by a JSON boot while obj_city still holds the NPC cities.\n");
|
||||
sql.append("DROP TABLE IF EXISTS `task25_city_map`;\n");
|
||||
sql.append("CREATE TABLE `task25_city_map` (`oldUID` BIGINT UNSIGNED NOT NULL, `newID` INT NULL, ")
|
||||
.append("`staticKey` VARCHAR(64) NULL, PRIMARY KEY (`oldUID`));\n");
|
||||
sql.append("INSERT INTO `task25_city_map` VALUES\n");
|
||||
|
||||
for (int i = 0; i < dbCities.size(); i++) {
|
||||
|
||||
entry = entriesByDbUID.get(dbCities.get(i).getObjectUUID());
|
||||
|
||||
if (entry == null)
|
||||
sql.append("(").append(dbCities.get(i).getObjectUUID()).append(", NULL, NULL)");
|
||||
else
|
||||
sql.append("(").append(dbCities.get(i).getObjectUUID()).append(", ").append(entry.staticID)
|
||||
.append(", '").append(entry.key).append("')");
|
||||
|
||||
sql.append(i == dbCities.size() - 1 ? ";\n" : ",\n");
|
||||
}
|
||||
|
||||
try {
|
||||
Files.write(migrationFile.toPath(), sql.toString().getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
Logger.error("Static city migration mapping not written: " + migrationFile.getAbsolutePath() + ": " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.info("Static city migration mapping: " + dbCities.size() + " rows written to " + migrationFile.getName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.gameManager;
|
||||
|
||||
import engine.mbEnums.GameObjectType;
|
||||
import engine.parsers.cache.CacheParserUtil;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
// W2 safety net: every static ID with its natural key, in load order, written to maps/<map>.manifest at boot.
|
||||
// Diffing against the previous boot's file shows IDs that moved; the orphan check shows DB references they broke.
|
||||
|
||||
public enum StaticManifest {
|
||||
|
||||
STATICMANIFEST;
|
||||
|
||||
private static final String mapPath = ConfigManager.DEFAULT_DATA_DIR + "maps/";
|
||||
|
||||
// Player data columns that can hold a static ID (findings §5, Task 2.1). A null type accepts any static object.
|
||||
private static final String[][] referenceColumns = {
|
||||
{"object", "parent", null},
|
||||
{"obj_character", "char_bindBuilding", "Building"},
|
||||
{"obj_character", "guildUID", "Guild"},
|
||||
{"dyn_character_guildhistory", "GuildID", "Guild"},
|
||||
{"dyn_guild_allianceenemylist", "OtherGuildID", "Guild"},
|
||||
{"dyn_guild_guildkoslist", "KOSGuildID", "Guild"},
|
||||
{"obj_mob", "mob_buildingID", "Building"},
|
||||
{"obj_mob", "mob_guildUID", "Guild"},
|
||||
{"obj_npc", "npc_buildingID", "Building"},
|
||||
{"obj_npc", "npc_guildID", "Guild"},
|
||||
{"obj_city", "treeOfLifeUUID", "Building"},
|
||||
{"obj_guild", "ownedCity", "City"},
|
||||
{"static_runegate_portals", "sourceBuilding", "Building"},
|
||||
{"static_runegate_portals", "targetBuilding", "Building"},
|
||||
{"obj_mine", "mine_buildingUID", "Building"}
|
||||
};
|
||||
|
||||
private static final int LOGGED_MOVES = 20;
|
||||
|
||||
private static final ArrayList<ManifestEntry> entries = new ArrayList<>();
|
||||
private static final HashMap<Integer, ManifestEntry> entriesByID = new HashMap<>();
|
||||
private static final HashMap<String, ManifestEntry> entriesByKey = new HashMap<>();
|
||||
|
||||
public static void addEntry(int staticID, GameObjectType objectType, String key) {
|
||||
|
||||
ManifestEntry entry = new ManifestEntry(staticID, objectType.name(), key);
|
||||
|
||||
if (entriesByKey.containsKey(key))
|
||||
throw new IllegalStateException("Static manifest key " + key + " is used twice");
|
||||
|
||||
entries.add(entry);
|
||||
entriesByID.put(staticID, entry);
|
||||
entriesByKey.put(key, entry);
|
||||
}
|
||||
|
||||
// Logs moved, vanished and re-targeted IDs against the previous manifest, then replaces it (old copy kept as .prev).
|
||||
// Moved IDs leave DB references on the wrong objects: they are written to <map>.renumber.sql and the count is
|
||||
// returned so boot can stop before play.
|
||||
|
||||
public static int writeManifest(int mapID) {
|
||||
|
||||
File manifestFile = new File(mapPath + mapID + ".manifest");
|
||||
File previousFile = new File(mapPath + mapID + ".manifest.prev");
|
||||
HashMap<String, ManifestEntry> previousByKey;
|
||||
StringBuilder manifestText = new StringBuilder();
|
||||
ArrayList<ManifestEntry[]> movedEntries = new ArrayList<>();
|
||||
|
||||
previousByKey = readManifest(manifestFile, mapID);
|
||||
|
||||
if (previousByKey != null) {
|
||||
diffManifest(previousByKey, movedEntries);
|
||||
|
||||
if (!movedEntries.isEmpty())
|
||||
writeRenumber(mapID, movedEntries);
|
||||
}
|
||||
|
||||
manifestText.append("{\n \"map\": ").append(mapID).append(",\n \"count\": ").append(entries.size())
|
||||
.append(",\n \"statics\": [\n");
|
||||
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
manifestText.append(" ").append(entries.get(i).toJson());
|
||||
manifestText.append(i < entries.size() - 1 ? ",\n" : "\n");
|
||||
}
|
||||
|
||||
manifestText.append(" ]\n}\n");
|
||||
|
||||
try {
|
||||
if (manifestFile.exists())
|
||||
Files.copy(manifestFile.toPath(), previousFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
Files.write(manifestFile.toPath(), manifestText.toString().getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
Logger.error("Static manifest not written: " + manifestFile.getAbsolutePath() + ": " + e);
|
||||
return movedEntries.size();
|
||||
}
|
||||
|
||||
Logger.info("Static manifest: " + entries.size() + " static IDs written to " + manifestFile.getName());
|
||||
|
||||
return movedEntries.size();
|
||||
}
|
||||
|
||||
// A renumber file is deleted by hand once its SQL ran; until then the DB still holds the old IDs.
|
||||
public static boolean isRenumberPending(int mapID) {
|
||||
return new File(mapPath + mapID + ".renumber.sql").exists();
|
||||
}
|
||||
|
||||
// static_renumber_map: old ID -> new ID per moved key, read by World-Boot-Plan sql/static_renumber.sql.
|
||||
private static void writeRenumber(int mapID, ArrayList<ManifestEntry[]> movedEntries) {
|
||||
|
||||
File renumberFile = new File(mapPath + mapID + ".renumber.sql");
|
||||
StringBuilder sql = new StringBuilder();
|
||||
ManifestEntry previousEntry;
|
||||
ManifestEntry currentEntry;
|
||||
|
||||
sql.append("-- Static IDs that moved since the previous manifest of map ").append(mapID).append(".\n");
|
||||
sql.append("DROP TABLE IF EXISTS `static_renumber_map`;\n");
|
||||
sql.append("CREATE TABLE `static_renumber_map` (`oldID` INT NOT NULL, `newID` INT NOT NULL, `type` VARCHAR(32) NOT NULL, ")
|
||||
.append("`staticKey` VARCHAR(64) NOT NULL, PRIMARY KEY (`oldID`));\n");
|
||||
|
||||
for (int i = 0; i < movedEntries.size(); i++) {
|
||||
|
||||
previousEntry = movedEntries.get(i)[0];
|
||||
currentEntry = movedEntries.get(i)[1];
|
||||
|
||||
if (i % 500 == 0)
|
||||
sql.append("INSERT INTO `static_renumber_map` VALUES\n");
|
||||
|
||||
sql.append("(").append(previousEntry.id).append(", ").append(currentEntry.id).append(", '")
|
||||
.append(currentEntry.type).append("', '").append(currentEntry.key).append("')");
|
||||
sql.append(i % 500 == 499 || i == movedEntries.size() - 1 ? ";\n" : ",\n");
|
||||
}
|
||||
|
||||
try {
|
||||
Files.write(renumberFile.toPath(), sql.toString().getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
Logger.error("Static renumber mapping not written: " + renumberFile.getAbsolutePath() + ": " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.warn("Static manifest: " + movedEntries.size() + " moved IDs written to " + renumberFile.getName());
|
||||
}
|
||||
|
||||
// Logs every negative DB reference (below -1, which means "none") that no loaded static object of the right type holds.
|
||||
|
||||
public static void logOrphanReferences() {
|
||||
|
||||
HashMap<Integer, Integer> negativeValues;
|
||||
ManifestEntry entry;
|
||||
String expectedType;
|
||||
String problem;
|
||||
int orphanColumns = 0;
|
||||
int orphanRows = 0;
|
||||
|
||||
for (String[] referenceColumn : referenceColumns) {
|
||||
|
||||
expectedType = referenceColumn[2];
|
||||
negativeValues = DbManager.StaticWorldQueries.GET_NEGATIVE_REFERENCES(referenceColumn[0], referenceColumn[1]);
|
||||
|
||||
if (negativeValues == null)
|
||||
continue;
|
||||
|
||||
for (Map.Entry<Integer, Integer> negativeValue : negativeValues.entrySet()) {
|
||||
|
||||
entry = entriesByID.get(negativeValue.getKey());
|
||||
|
||||
if (entry == null)
|
||||
problem = "no static object";
|
||||
else if (DbManager.getFromCache(GameObjectType.valueOf(entry.type), entry.id) == null)
|
||||
problem = entry.type + " " + entry.key + " is not loaded";
|
||||
else if (expectedType != null && !expectedType.equals(entry.type))
|
||||
problem = "points at " + entry.type + " " + entry.key + ", expected " + expectedType;
|
||||
else
|
||||
continue;
|
||||
|
||||
Logger.warn("Orphan reference: " + referenceColumn[0] + "." + referenceColumn[1] + " = "
|
||||
+ negativeValue.getKey() + " on " + negativeValue.getValue() + " rows: " + problem);
|
||||
orphanColumns++;
|
||||
orphanRows += negativeValue.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Static orphan check: " + orphanColumns + " orphan values on " + orphanRows + " rows");
|
||||
}
|
||||
|
||||
private static HashMap<String, ManifestEntry> readManifest(File manifestFile, int mapID) {
|
||||
|
||||
HashMap<String, ManifestEntry> previousByKey = new HashMap<>();
|
||||
JSONObject manifestJson;
|
||||
JSONArray staticsJson;
|
||||
JSONObject entryJson;
|
||||
|
||||
if (!manifestFile.exists()) {
|
||||
Logger.info("Static manifest: no previous " + manifestFile.getName() + ", nothing to diff");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
manifestJson = CacheParserUtil.readJsonFile(manifestFile);
|
||||
|
||||
if (manifestJson.getInt("map") != mapID) {
|
||||
Logger.error(manifestFile.getName() + ": map " + manifestJson.getInt("map") + " does not match MB_WORLD_MAPID " + mapID);
|
||||
return null;
|
||||
}
|
||||
|
||||
staticsJson = manifestJson.getJSONArray("statics");
|
||||
|
||||
for (int i = 0; i < staticsJson.length(); i++) {
|
||||
entryJson = staticsJson.getJSONObject(i);
|
||||
previousByKey.put(entryJson.getString("key"),
|
||||
new ManifestEntry(entryJson.getInt("id"), entryJson.getString("type"), entryJson.getString("key")));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error("Previous static manifest unreadable, nothing to diff: " + manifestFile.getName() + ": " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return previousByKey;
|
||||
}
|
||||
|
||||
// A re-targeted ID is the dangerous case: player references to it now silently point at a different object.
|
||||
|
||||
private static void diffManifest(HashMap<String, ManifestEntry> previousByKey, ArrayList<ManifestEntry[]> movedEntries) {
|
||||
|
||||
HashMap<Integer, ManifestEntry> previousByID = new HashMap<>();
|
||||
ManifestEntry currentEntry;
|
||||
ManifestEntry previousEntry;
|
||||
int moved = 0;
|
||||
int vanished = 0;
|
||||
int retargeted = 0;
|
||||
int added = 0;
|
||||
|
||||
for (ManifestEntry entry : previousByKey.values())
|
||||
previousByID.put(entry.id, entry);
|
||||
|
||||
for (ManifestEntry entry : previousByKey.values()) {
|
||||
|
||||
currentEntry = entriesByKey.get(entry.key);
|
||||
|
||||
if (currentEntry == null) {
|
||||
Logger.warn("Static manifest: " + entry.type + " " + entry.key + " (was " + entry.id + ") vanished");
|
||||
vanished++;
|
||||
} else if (currentEntry.id != entry.id) {
|
||||
|
||||
if (moved < LOGGED_MOVES)
|
||||
Logger.warn("Static manifest: " + entry.type + " " + entry.key + " moved " + entry.id + " -> " + currentEntry.id);
|
||||
|
||||
movedEntries.add(new ManifestEntry[]{entry, currentEntry});
|
||||
moved++;
|
||||
}
|
||||
}
|
||||
|
||||
for (ManifestEntry entry : entries) {
|
||||
|
||||
previousEntry = previousByID.get(entry.id);
|
||||
|
||||
if (!previousByKey.containsKey(entry.key))
|
||||
added++;
|
||||
|
||||
if (previousEntry != null && !previousEntry.key.equals(entry.key)) {
|
||||
|
||||
if (retargeted < LOGGED_MOVES)
|
||||
Logger.warn("Static manifest: ID " + entry.id + " re-targeted from " + previousEntry.type + " "
|
||||
+ previousEntry.key + " to " + entry.type + " " + entry.key);
|
||||
|
||||
retargeted++;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Static manifest diff: " + moved + " moved, " + vanished + " vanished, " + retargeted
|
||||
+ " re-targeted, " + added + " new");
|
||||
}
|
||||
|
||||
private static class ManifestEntry {
|
||||
|
||||
private final int id;
|
||||
private final String type;
|
||||
private final String key;
|
||||
|
||||
private ManifestEntry(int id, String type, String key) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
private String toJson() {
|
||||
return "{\"id\": " + id + ", \"type\": " + JSONObject.quote(type) + ", \"key\": " + JSONObject.quote(key) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,10 @@ import engine.mbEnums;
|
||||
import engine.objects.Building;
|
||||
import engine.objects.City;
|
||||
import engine.objects.Zone;
|
||||
import engine.objects.ZoneTemplate;
|
||||
import engine.parsers.map.ZoneTreeParser;
|
||||
import engine.parsers.objects.CZoneProp;
|
||||
import engine.parsers.objects.CZoneTemplate;
|
||||
import engine.parsers.objects.ZoneTreeEntry;
|
||||
import engine.server.MBServerStatics;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
@@ -36,7 +39,11 @@ public enum ZoneManager {
|
||||
|
||||
ZONEMANAGER;
|
||||
|
||||
public static HashMap<Integer, ZoneTemplate> _zone_templates = new HashMap<>();
|
||||
public static HashMap<Integer, CZoneTemplate> _zone_templates = new HashMap<>();
|
||||
public static ArrayList<ZoneTreeEntry> zoneTree;
|
||||
public static final HashMap<Integer, Zone> staticZonesByTreeUID = new HashMap<>();
|
||||
public static final HashMap<Integer, Integer> staticIDsByTreeUID = new HashMap<>();
|
||||
public static final ArrayList<Zone> ruinZones = new ArrayList<>();
|
||||
|
||||
public static final Set<Zone> macroZones = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
private static final ConcurrentHashMap<Integer, Zone> zonesByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD);
|
||||
@@ -51,6 +58,89 @@ public enum ZoneManager {
|
||||
/* Instance variables */
|
||||
public static Zone seaFloor = null;
|
||||
|
||||
// CZONE templates plus the server's player-city template 0, which has no CZONE file
|
||||
|
||||
public static void loadZoneTemplates() {
|
||||
|
||||
CZoneTemplate playerCityTemplate = new CZoneTemplate();
|
||||
|
||||
_zone_templates.putAll(CZoneTemplate._zones);
|
||||
|
||||
playerCityTemplate.template_id = 0;
|
||||
playerCityTemplate.zone_sea_level_type = mbEnums.SeaLevelType.PARENT;
|
||||
playerCityTemplate.zone_sea_level = 0;
|
||||
playerCityTemplate.zone_max_blend = 128;
|
||||
playerCityTemplate.zone_min_blend = 128;
|
||||
playerCityTemplate.terrain_max_y = 5;
|
||||
playerCityTemplate.zone_major_radius = (int) mbEnums.CityBoundsType.ZONE.halfExtents;
|
||||
playerCityTemplate.zone_minor_radius = (int) mbEnums.CityBoundsType.ZONE.halfExtents;
|
||||
playerCityTemplate.terrain_type = mbEnums.TerrainGenType.PLANAR;
|
||||
|
||||
_zone_templates.put(playerCityTemplate.template_id, playerCityTemplate);
|
||||
|
||||
Logger.info("Zone templates: " + _zone_templates.size());
|
||||
}
|
||||
|
||||
// Read before the zone templates, so only the templates this map uses are parsed. False aborts boot.
|
||||
|
||||
public static boolean loadZoneTree(int mapID) {
|
||||
|
||||
zoneTree = ZoneTreeParser.parseZoneTree(mapID);
|
||||
|
||||
return zoneTree != null;
|
||||
}
|
||||
|
||||
public static TreeSet<Integer> getZoneTreeTemplateIDs() {
|
||||
|
||||
TreeSet<Integer> templateIDs = new TreeSet<>();
|
||||
|
||||
for (ZoneTreeEntry treeEntry : zoneTree)
|
||||
templateIDs.add(treeEntry.template);
|
||||
|
||||
return templateIDs;
|
||||
}
|
||||
|
||||
// Static zones from the map's zone tree, then player-city zones from the DB. Every static zone takes its
|
||||
// static ID in tree order (W10); zones use it as their ID only once static buildings load from JSON (2.4).
|
||||
// False aborts boot.
|
||||
|
||||
public static boolean loadZones(boolean useStaticIDs) {
|
||||
|
||||
ArrayList<Zone> cityZones;
|
||||
Zone zone;
|
||||
int staticID;
|
||||
int parentZoneID;
|
||||
int staticCount = 0;
|
||||
|
||||
for (ZoneTreeEntry treeEntry : zoneTree) {
|
||||
|
||||
if (treeEntry.parent != 0 && !staticZonesByTreeUID.containsKey(treeEntry.parent)) {
|
||||
Logger.error("Zone " + treeEntry.uid + " (" + treeEntry.name + ") has no parent zone " + treeEntry.parent);
|
||||
return false;
|
||||
}
|
||||
|
||||
staticID = DbManager.nextStaticID(mbEnums.GameObjectType.Zone, "zone:" + treeEntry.uid);
|
||||
staticIDsByTreeUID.put(treeEntry.uid, staticID);
|
||||
|
||||
if (treeEntry.parent == 0)
|
||||
parentZoneID = 0;
|
||||
else
|
||||
parentZoneID = staticZonesByTreeUID.get(treeEntry.parent).getObjectUUID();
|
||||
|
||||
zone = Zone.createStaticZone(treeEntry, useStaticIDs ? staticID : treeEntry.uid, parentZoneID);
|
||||
DbManager.addToCache(zone);
|
||||
zone.runAfterLoad();
|
||||
staticZonesByTreeUID.put(treeEntry.uid, zone);
|
||||
staticCount++;
|
||||
}
|
||||
|
||||
cityZones = DbManager.ZoneQueries.GET_PLAYER_CITY_ZONES();
|
||||
|
||||
Logger.info("Zones: " + staticCount + " static, " + cityZones.size() + " player city");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find all zones coordinates fit into, starting with Sea Floor
|
||||
|
||||
public static ArrayList<Zone> getAllZonesIn(final Vector3fImmutable loc) {
|
||||
@@ -139,6 +229,46 @@ public enum ZoneManager {
|
||||
return ZoneManager.zonesByName.get(zoneName);
|
||||
}
|
||||
|
||||
// Ruins: static zones whose top-level CITY props carry a seed ruin key (the 8 Ruins on map 108). Needs the guild seed.
|
||||
public static void loadRuinZones() {
|
||||
|
||||
Zone zone;
|
||||
|
||||
for (ZoneTreeEntry treeEntry : ZoneManager.zoneTree) {
|
||||
|
||||
zone = ZoneManager.staticZonesByTreeUID.get(treeEntry.uid);
|
||||
|
||||
for (CZoneProp prop : zone.template.zone_prop_info)
|
||||
if (prop.prop_type == mbEnums.ZonePropType.CITY && GuildManager._ruinKeys.contains(prop.city_prop_gm_guild)) {
|
||||
ZoneManager.ruinZones.add(zone);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Ruins: " + ZoneManager.ruinZones.size() + " zones" + (ConfigManager.MB_USE_RUINS.getValue().equalsIgnoreCase("true")
|
||||
? "" : " (MB_USE_RUINS off: respawn at Sea Dog's Rest)"));
|
||||
}
|
||||
|
||||
// Respawn point of a character with no usable bind: a random Ruin, or Sea Dog's Rest when ruins are off or missing.
|
||||
public static Vector3fImmutable getRandomRuinLocation() {
|
||||
|
||||
Zone ruinZone;
|
||||
|
||||
if (ConfigManager.MB_USE_RUINS.getValue().equalsIgnoreCase("true") && !ZoneManager.ruinZones.isEmpty()) {
|
||||
ruinZone = ZoneManager.ruinZones.get(ThreadLocalRandom.current().nextInt(ZoneManager.ruinZones.size()));
|
||||
return Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc(), 30);
|
||||
}
|
||||
|
||||
ruinZone = ZoneManager.getZoneByName("sea dog's rest");
|
||||
|
||||
// 14001 does not have a banestone to bind at
|
||||
|
||||
if (ruinZone.templateID == 14001)
|
||||
return Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc(), 30);
|
||||
|
||||
return Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc().add(new Vector3fImmutable(-196.016f, 2.812f, 203.621f)), 30);
|
||||
}
|
||||
|
||||
public static Collection<Zone> getAllZones() {
|
||||
return ZoneManager.zonesByUUID.values();
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ public class Bounds {
|
||||
|
||||
blueprint = building.getBlueprint();
|
||||
|
||||
this.quaternion = new Quaternion(building.getRot().x, building.getRot().y, building.getRot().z, building.getw());
|
||||
this.quaternion = new Quaternion(building.getRotation().x, building.getRotation().y, building.getRotation().z, building.getRotation().w);
|
||||
|
||||
// Calculate Bounds for non-blueprint objects
|
||||
|
||||
@@ -437,7 +437,7 @@ public class Bounds {
|
||||
|
||||
// The rotation is reset after the new aabb is calculated.
|
||||
|
||||
this.rotation = building.getRot().y;
|
||||
this.rotation = building.getRotation().y;
|
||||
|
||||
// Caclculate and set the new half halfExtents for the rotated bounding box
|
||||
// and reset the rotation to 0 for this bounds.
|
||||
@@ -451,7 +451,7 @@ public class Bounds {
|
||||
}
|
||||
|
||||
this.origin.set(building.getLoc().x, building.getLoc().z);
|
||||
this.rotation = building.getRot().y;
|
||||
this.rotation = building.getRotation().y;
|
||||
this.halfExtents.set(blueprint.getExtents());
|
||||
this.flipExtents = Bounds.calculateFlipExtents(this);
|
||||
|
||||
|
||||
@@ -1142,6 +1142,17 @@ public class Quaternion {
|
||||
return opposite(this);
|
||||
}
|
||||
|
||||
// Client MakeRotateY (Math.dll 0x10002960): w = cos θ/2, y = sin θ/2, radians.
|
||||
// Math rather than FastMath: FastMath.reduceSinAngle mis-reduces angles below -π.
|
||||
public static Quaternion fromYaw(float yaw) {
|
||||
return new Quaternion(0f, (float) Math.sin(yaw * 0.5), 0f, (float) Math.cos(yaw * 0.5));
|
||||
}
|
||||
|
||||
// The angle MakeRotateY would need to produce this rotation; characters send it as one float.
|
||||
public float getYaw() {
|
||||
return 2 * FastMath.atan2(y, w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Quaternion clone() {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.math;
|
||||
|
||||
// The client's placement (ArcObjectInfo +0x28/+0x34/+0x44, math::Transformation); see MBDATA/specs/WorldPlacement.md §7.
|
||||
|
||||
public class Transform {
|
||||
|
||||
public final Vector3fImmutable location;
|
||||
public final Quaternion rotation;
|
||||
public final Vector3f scale;
|
||||
|
||||
public Transform(Vector3fImmutable location, Quaternion rotation, Vector3f scale) {
|
||||
this.location = location;
|
||||
this.rotation = rotation;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public static Transform fromYaw(Vector3fImmutable location, float yaw) {
|
||||
return new Transform(location, Quaternion.fromYaw(yaw), new Vector3f(1.0f, 1.0f, 1.0f));
|
||||
}
|
||||
|
||||
// world = parent × local, as the client composes zone and object transforms (sb.exe 0x6516C0).
|
||||
public static Transform mult(Transform parent, Transform local) {
|
||||
|
||||
Vector3f scaledLocation;
|
||||
Vector3f rotatedLocation;
|
||||
Quaternion rotation;
|
||||
Vector3f scale;
|
||||
|
||||
scaledLocation = new Vector3f(local.location).mult(parent.scale);
|
||||
rotatedLocation = parent.rotation.mult(scaledLocation);
|
||||
rotation = parent.rotation.mult(local.rotation);
|
||||
scale = parent.scale.mult(local.scale);
|
||||
|
||||
return new Transform(parent.location.add(rotatedLocation), rotation, scale);
|
||||
}
|
||||
|
||||
}
|
||||
+52
-60
@@ -8,15 +8,11 @@
|
||||
|
||||
package engine;
|
||||
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.gameManager.PowersManager;
|
||||
import engine.gameManager.ZoneManager;
|
||||
import engine.math.Vector2f;
|
||||
import engine.math.Vector3f;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.objects.AbstractCharacter;
|
||||
import engine.objects.Shrine;
|
||||
import engine.objects.Zone;
|
||||
import engine.powers.EffectsBase;
|
||||
import engine.powers.RunePowerEntry;
|
||||
import org.pmw.tinylog.Logger;
|
||||
@@ -25,7 +21,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/*
|
||||
* MagicBane engine enumeration class.
|
||||
@@ -504,61 +499,6 @@ public class mbEnums {
|
||||
|
||||
}
|
||||
|
||||
public enum Ruins {
|
||||
|
||||
ESTRAGOTH(569),
|
||||
KARFELL(570),
|
||||
MORELAN(571),
|
||||
REGARS(572),
|
||||
HALLOS(573),
|
||||
WESTERMORE(574),
|
||||
EYWAN(575),
|
||||
CAER(576);
|
||||
|
||||
private final int zoneUUID;
|
||||
|
||||
Ruins(int uuid) {
|
||||
this.zoneUUID = uuid;
|
||||
}
|
||||
|
||||
public static Ruins getRandomRuin() {
|
||||
|
||||
Ruins ruins;
|
||||
|
||||
ruins = Ruins.values()[ThreadLocalRandom.current()
|
||||
.nextInt(Ruins.values().length)];
|
||||
|
||||
return ruins;
|
||||
}
|
||||
|
||||
public Vector3fImmutable getLocation() {
|
||||
|
||||
Zone ruinZone;
|
||||
Vector3fImmutable spawnLocation;
|
||||
|
||||
// Send to SDR if so configured
|
||||
|
||||
if (ConfigManager.MB_USE_RUINS.getValue().equalsIgnoreCase("true")) {
|
||||
ruinZone = ZoneManager.getZoneByUUID(this.zoneUUID);
|
||||
spawnLocation = Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc(), 30);
|
||||
} else {
|
||||
ruinZone = ZoneManager.getZoneByName("sea dog's rest");
|
||||
|
||||
// 14001 does not have a banestone to bind at
|
||||
|
||||
if (ruinZone.templateID == 14001)
|
||||
spawnLocation = Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc(), 30);
|
||||
else
|
||||
spawnLocation = Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc()
|
||||
.add(new Vector3fImmutable(-196.016f, 2.812f, 203.621f)), 30);
|
||||
}
|
||||
|
||||
|
||||
return spawnLocation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum Guards {
|
||||
|
||||
HumanArcher(13.97f, 13.97f, 6.19f, 4.2199998f, 18.38f, 6.33f, 6.5f),
|
||||
@@ -3147,6 +3087,58 @@ public class mbEnums {
|
||||
MULTIPLY
|
||||
}
|
||||
|
||||
// CZONE value sets; only the values present in the shipped cache.
|
||||
|
||||
public enum ZoneShapeType {
|
||||
ELLIPTICAL,
|
||||
RECTANGULAR
|
||||
}
|
||||
|
||||
public enum SeaLevelType {
|
||||
PARENT,
|
||||
SELF,
|
||||
WORLD
|
||||
}
|
||||
|
||||
// NONE is the server's marker for a zone with no zone_terrain_gen block
|
||||
|
||||
public enum TerrainGenType {
|
||||
NONE,
|
||||
TARGA,
|
||||
PLANAR
|
||||
}
|
||||
|
||||
public enum ZonePropType {
|
||||
PROP,
|
||||
CITY
|
||||
}
|
||||
|
||||
public enum ZoneMobileType {
|
||||
MOBILE,
|
||||
TRAINER
|
||||
}
|
||||
|
||||
// MB_STATIC_BUILDINGS (World Boot Plan 2.4). DB: static buildings from obj_building, before the migration.
|
||||
// PARITY: DB load plus the JSON buildings compared and the migration mapping written. JSON: after the migration.
|
||||
public enum StaticBuildingMode {
|
||||
DB,
|
||||
PARITY,
|
||||
JSON;
|
||||
|
||||
public static StaticBuildingMode fromConfig(String configValue) {
|
||||
|
||||
for (StaticBuildingMode mode : values())
|
||||
if (mode.name().equalsIgnoreCase(configValue.trim()))
|
||||
return mode;
|
||||
|
||||
return DB;
|
||||
}
|
||||
}
|
||||
|
||||
public enum PointSetType {
|
||||
POINTS_ORDEREDREV
|
||||
}
|
||||
|
||||
public enum ServerConfig {
|
||||
MBDB_108("AERYNTH", "ARAC", "MOURNING", 300000),
|
||||
MBDB_192("DALGOTH", "ARAC", "GOOK", 300001),
|
||||
|
||||
@@ -62,6 +62,12 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
|
||||
if (vendor == null)
|
||||
return true;
|
||||
|
||||
// Same access rule as opening the vendor's management window (OrderNPCMsgHandler); never trust the client
|
||||
|
||||
if (!BuildingManager.PlayerCanControlNotOwner(vendor.getBuilding(), player)
|
||||
&& !BuildingManager.playerCanManage(player, vendor.getBuilding()))
|
||||
return true;
|
||||
|
||||
// Process Request
|
||||
|
||||
switch (msg.actionType) {
|
||||
@@ -299,6 +305,11 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
|
||||
if (vendor.getBuilding() == null)
|
||||
return;
|
||||
|
||||
// NPC buildings have no strongbox to refund into
|
||||
|
||||
if (vendor.getBuilding().getProtectionState() == mbEnums.ProtectionState.NPC)
|
||||
return;
|
||||
|
||||
PlayerCharacter player = origin.getPlayerCharacter();
|
||||
|
||||
if (player == null)
|
||||
|
||||
@@ -1180,10 +1180,10 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
|
||||
|
||||
// if W return is negative, this is a -90 rotation not a 90?
|
||||
|
||||
newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
|
||||
currentZone.getObjectUUID(), playerCharacter.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||
newMesh = Building.persistBuilding(Building.createBuilding(
|
||||
currentZone, playerCharacter.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
|
||||
completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
|
||||
completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation));
|
||||
|
||||
// Make sure we have a valid mesh
|
||||
|
||||
@@ -1303,10 +1303,10 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
|
||||
DateTime completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));
|
||||
|
||||
|
||||
newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
|
||||
currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||
newMesh = Building.persistBuilding(Building.createBuilding(
|
||||
currentZone, player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
|
||||
completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
|
||||
completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation));
|
||||
|
||||
// Make sure we have a valid mesh
|
||||
if (newMesh == null) {
|
||||
@@ -1342,10 +1342,10 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
|
||||
|
||||
City city = City.getCity(currentZone.playerCityUUID);
|
||||
|
||||
warehouseBuilding = DbManager.BuildingQueries.CREATE_BUILDING(
|
||||
currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||
warehouseBuilding = Building.persistBuilding(Building.createBuilding(
|
||||
currentZone, player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
|
||||
DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
|
||||
DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation));
|
||||
|
||||
if (warehouseBuilding == null) {
|
||||
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
|
||||
@@ -1363,7 +1363,6 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
|
||||
MaintenanceManager.setMaintDateTime(warehouseBuilding, LocalDateTime.now().plusDays(7));
|
||||
warehouseBuilding.setLoc(warehouseBuilding.getLoc());
|
||||
InterestManager.setObjectDirty(warehouseBuilding);
|
||||
warehouseBuilding.runAfterLoad();
|
||||
|
||||
if (city == null)
|
||||
return true;
|
||||
|
||||
@@ -21,6 +21,7 @@ import engine.jobs.PersistentAoeJob;
|
||||
import engine.jobs.TrackJob;
|
||||
import engine.math.AtomicFloat;
|
||||
import engine.math.Bounds;
|
||||
import engine.math.Quaternion;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import engine.mbEnums.*;
|
||||
@@ -536,6 +537,18 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
||||
|
||||
}
|
||||
|
||||
// The one yaw float LOADCHARACTER carries; a character in a building gets the building's yaw added.
|
||||
public float getClientYaw() {
|
||||
|
||||
float yaw = this.getRotation().getYaw();
|
||||
|
||||
if (this.building != null)
|
||||
if (this.building.getBounds() != null && this.building.getBounds().getQuaternion() != null)
|
||||
yaw += this.building.getBounds().getQuaternion().angleY;
|
||||
|
||||
return yaw;
|
||||
}
|
||||
|
||||
public static void teleport(AbstractCharacter abstractCharacter, final Vector3fImmutable targetLoc) {
|
||||
abstractCharacter.locationLock.writeLock().lock();
|
||||
try {
|
||||
|
||||
@@ -18,7 +18,7 @@ import engine.job.JobScheduler;
|
||||
import engine.jobs.NoTimeJob;
|
||||
import engine.math.AtomicFloat;
|
||||
import engine.math.Bounds;
|
||||
import engine.math.Vector3f;
|
||||
import engine.math.Quaternion;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums.DispatchChannel;
|
||||
import engine.mbEnums.EffectSourceType;
|
||||
@@ -56,9 +56,11 @@ public abstract class AbstractWorldObject extends AbstractGameObject {
|
||||
protected boolean movingUp = false;
|
||||
private String name = "";
|
||||
private byte tier = 0;
|
||||
private Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f);
|
||||
private Quaternion rotation = new Quaternion();
|
||||
private int objectTypeMask = 0;
|
||||
private Bounds bounds;
|
||||
// Built from world data with an ID from DbManager.nextStaticID(); never written to the DB.
|
||||
private boolean staticWorldObject = false;
|
||||
|
||||
/**
|
||||
* No Id Constructor
|
||||
@@ -528,13 +530,13 @@ public abstract class AbstractWorldObject extends AbstractGameObject {
|
||||
|
||||
}
|
||||
|
||||
public Vector3f getRot() {
|
||||
return rot;
|
||||
public Quaternion getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public void setRot(Vector3f rotation) {
|
||||
synchronized (this.rot) {
|
||||
this.rot = rotation;
|
||||
public void setRotation(Quaternion rotation) {
|
||||
synchronized (this.rotation) {
|
||||
this.rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,7 +545,7 @@ public abstract class AbstractWorldObject extends AbstractGameObject {
|
||||
}
|
||||
|
||||
public void setTier(byte tier) {
|
||||
synchronized (this.rot) {
|
||||
synchronized (this.rotation) {
|
||||
this.tier = tier;
|
||||
}
|
||||
}
|
||||
@@ -594,6 +596,14 @@ public abstract class AbstractWorldObject extends AbstractGameObject {
|
||||
this.bounds = bounds;
|
||||
}
|
||||
|
||||
public boolean isStaticWorldObject() {
|
||||
return this.staticWorldObject;
|
||||
}
|
||||
|
||||
public void setStaticWorldObject(boolean staticWorldObject) {
|
||||
this.staticWorldObject = staticWorldObject;
|
||||
}
|
||||
|
||||
public float getAltitude() {
|
||||
return altitude;
|
||||
}
|
||||
|
||||
@@ -210,10 +210,10 @@ public final class Bane {
|
||||
return false;
|
||||
}
|
||||
|
||||
Building stone = DbManager.BuildingQueries.CREATE_BUILDING(
|
||||
cityZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(),
|
||||
Building stone = Building.persistBuilding(Building.createBuilding(
|
||||
cityZone, player.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(),
|
||||
localLocation, 1.0f, blueprint.getMaxHealth(rank), ProtectionState.PROTECTED, 0, rank,
|
||||
null, blueprint.getBlueprintUUID(), 1, 0.0f);
|
||||
null, blueprint.getBlueprintUUID(), 1, 0.0f));
|
||||
|
||||
if (stone == null) {
|
||||
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
|
||||
|
||||
@@ -24,6 +24,8 @@ import engine.job.JobScheduler;
|
||||
import engine.jobs.DoorCloseJob;
|
||||
import engine.jobs.SiegeSpireWithdrawlJob;
|
||||
import engine.math.Bounds;
|
||||
import engine.math.Quaternion;
|
||||
import engine.math.Transform;
|
||||
import engine.math.Vector3f;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
@@ -31,11 +33,15 @@ import engine.mbEnums.*;
|
||||
import engine.net.ByteBufferWriter;
|
||||
import engine.net.client.msg.ApplyBuildingEffectMsg;
|
||||
import engine.net.client.msg.UpdateObjectMsg;
|
||||
import engine.parsers.objects.CZonePlacement;
|
||||
import engine.parsers.objects.CZoneProp;
|
||||
import engine.server.MBServerStatics;
|
||||
import org.joda.time.DateTime;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
@@ -83,7 +89,6 @@ public class Building extends AbstractWorldObject {
|
||||
public ArrayList<Vector3fImmutable> patrolPoints;
|
||||
public ProtectionState protectionState = ProtectionState.NONE;
|
||||
protected Resists resists;
|
||||
private float w = 1.0f;
|
||||
private Vector3f meshScale = new Vector3f(1.0f, 1.0f, 1.0f);
|
||||
private int doorState = 0;
|
||||
private int ownerUUID = 0; //NPC or Character--check ownerIsNPC flag
|
||||
@@ -99,6 +104,14 @@ public class Building extends AbstractWorldObject {
|
||||
private ConcurrentHashMap<Integer, Condemned> condemned;
|
||||
private ArrayList<Building> children = null;
|
||||
|
||||
/**
|
||||
* No Id Constructor
|
||||
*/
|
||||
|
||||
public Building() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* ResultSet Constructor
|
||||
*/
|
||||
@@ -118,8 +131,7 @@ public class Building extends AbstractWorldObject {
|
||||
this.ownerUUID = rs.getInt("ownerUUID");
|
||||
this.doorState = rs.getInt("doorState");
|
||||
this.setHealth(rs.getInt("currentHP"));
|
||||
this.w = rs.getFloat("w");
|
||||
this.setRot(new Vector3f(0f, rs.getFloat("rotY"), 0f));
|
||||
this.setRotation(new Quaternion(0f, rs.getFloat("rotY"), 0f, rs.getFloat("w")));
|
||||
this.reverseKOS = rs.getByte("reverseKOS") == 1;
|
||||
this.statLat = rs.getFloat("locationX");
|
||||
this.statAlt = rs.getFloat("locationY");
|
||||
@@ -173,6 +185,122 @@ public class Building extends AbstractWorldObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Defaults mirror the obj_building column defaults so a new building matches a reloaded one.
|
||||
public static Building createBuilding(Zone parentZone, int ownerUUID, String name, int meshUUID,
|
||||
Vector3fImmutable localLoc, float scale, int currentHP,
|
||||
ProtectionState protectionState, int currentGold, int rank,
|
||||
DateTime upgradeDate, int blueprintUUID, float w, float rotY) {
|
||||
|
||||
Building building = new Building();
|
||||
|
||||
building.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
|
||||
building.gridObjectType = GridObjectType.STATIC;
|
||||
building.parentZoneUUID = parentZone.getObjectUUID();
|
||||
building.ownerUUID = ownerUUID;
|
||||
building.name = name;
|
||||
building.meshUUID = meshUUID;
|
||||
building.blueprintUUID = blueprintUUID;
|
||||
building.statLat = localLoc.x;
|
||||
building.statAlt = localLoc.y;
|
||||
building.statLon = localLoc.z;
|
||||
building.meshScale = new Vector3f(scale, scale, scale);
|
||||
building.setHealth(currentHP);
|
||||
building.protectionState = protectionState;
|
||||
building._strongboxValue = currentGold;
|
||||
building.maxGold = 15000000;
|
||||
building.rank = rank;
|
||||
building.setRotation(new Quaternion(0f, rotY, 0f, w));
|
||||
building.parentBuildingID = 0;
|
||||
building.floor = -1;
|
||||
building.level = -1;
|
||||
|
||||
if (upgradeDate != null)
|
||||
building.upgradeDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(upgradeDate.getMillis()), ZoneId.systemDefault());
|
||||
|
||||
return building;
|
||||
}
|
||||
|
||||
// A static building from a CZONE prop (W4). Children keep their local CZONE position and yaw, as live
|
||||
// servers sent them; top-level props sit on the terrain, since live servers ignored the CZONE Y of
|
||||
// top-level props (World-Boot-Plan findings, Task 2.3). ID 0 keeps the building out of the world grid.
|
||||
public static Building createStaticBuilding(int buildingID, Zone parentZone, CZoneProp prop, Building parentBuilding, int ownerUUID) {
|
||||
|
||||
Building building = new Building();
|
||||
CZonePlacement placement = prop.prop_basic_zone;
|
||||
Vector3fImmutable localLoc = placement.basic_zone_spawn_location;
|
||||
|
||||
building.setObjectUUID(buildingID);
|
||||
building.setStaticWorldObject(true);
|
||||
building.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
|
||||
building.gridObjectType = GridObjectType.STATIC;
|
||||
building.parentZoneUUID = parentZone.getObjectUUID();
|
||||
building.name = placement.basic_zone_name_override;
|
||||
building.meshUUID = placement.basic_zone_template_id;
|
||||
building.statLat = localLoc.x;
|
||||
building.statAlt = parentBuilding == null ? 0 : localLoc.y;
|
||||
building.statLon = localLoc.z;
|
||||
building.meshScale = new Vector3f(1.0f, 1.0f, 1.0f);
|
||||
|
||||
// CZONE y_rot is not the client's yaw: live servers sent π − y_rot (specs/WorldPlacement.md §3, W8).
|
||||
building.setRotation(Quaternion.fromYaw((float) (Math.PI - placement.basic_zone_y_rot)));
|
||||
|
||||
building.protectionState = ProtectionState.NPC;
|
||||
building.maxGold = 15000000;
|
||||
building.ownerUUID = ownerUUID;
|
||||
building.ownerIsNPC = ownerUUID != 0;
|
||||
|
||||
if (prop.prop_type == ZonePropType.CITY) {
|
||||
building.rank = prop.city_prop_asset_rank;
|
||||
building._strongboxValue = prop.city_prop_asset_funds;
|
||||
}
|
||||
|
||||
// Same swap as the DB rows; runAfterLoad's region lookup expects it.
|
||||
building.floor = placement.basic_zone_level_number;
|
||||
building.level = placement.basic_zone_room_number;
|
||||
|
||||
building.isFurniture = parentBuilding != null;
|
||||
building.parentBuildingID = parentBuilding == null ? 0 : parentBuilding.getObjectUUID();
|
||||
building.setLoc(getStaticWorldLocation(building, parentZone, parentBuilding));
|
||||
|
||||
return building;
|
||||
}
|
||||
|
||||
// Top-level: zone position on the terrain. Child: parent × local, so the parent's yaw turns the offset.
|
||||
private static Vector3fImmutable getStaticWorldLocation(Building building, Zone parentZone, Building parentBuilding) {
|
||||
|
||||
Vector3fImmutable groundLoc;
|
||||
Transform parentTransform;
|
||||
Transform localTransform;
|
||||
|
||||
if (parentBuilding == null) {
|
||||
groundLoc = new Vector3fImmutable(building.statLat + parentZone.absX, 0, building.statLon + parentZone.absZ);
|
||||
return groundLoc.setY(Terrain.getWorldHeight(groundLoc) + building.statAlt);
|
||||
}
|
||||
|
||||
parentTransform = new Transform(parentBuilding.getLoc(), parentBuilding.getRotation(), parentBuilding.meshScale);
|
||||
localTransform = new Transform(new Vector3fImmutable(building.statLat, building.statAlt, building.statLon),
|
||||
building.getRotation(), building.meshScale);
|
||||
|
||||
return Transform.mult(parentTransform, localTransform).location;
|
||||
}
|
||||
|
||||
// Writes a new player building once and loads it into the simulation; returns null if the insert failed.
|
||||
public static Building persistBuilding(Building building) {
|
||||
|
||||
int objectUUID;
|
||||
|
||||
objectUUID = DbManager.BuildingQueries.PERSIST(building);
|
||||
|
||||
if (objectUUID <= 0)
|
||||
return null;
|
||||
|
||||
building.setObjectUUID(objectUUID);
|
||||
DbManager.addToCache(building);
|
||||
building.runAfterLoad();
|
||||
|
||||
return building;
|
||||
}
|
||||
|
||||
/*
|
||||
* Getters
|
||||
*/
|
||||
@@ -198,11 +326,11 @@ public class Building extends AbstractWorldObject {
|
||||
writer.putFloat(building.getLoc().getZ());
|
||||
}
|
||||
|
||||
writer.putFloat(building.w);
|
||||
writer.putFloat(0f);
|
||||
writer.putFloat(building.getRot().y);
|
||||
writer.putFloat(building.getRotation().w);
|
||||
writer.putFloat(building.getRotation().x);
|
||||
writer.putFloat(building.getRotation().y);
|
||||
writer.putFloat(building.getRotation().z);
|
||||
|
||||
writer.putFloat(0f);
|
||||
writer.putFloat(building.meshScale.getX());
|
||||
writer.putFloat(building.meshScale.getY());
|
||||
writer.putFloat(building.meshScale.getZ());
|
||||
@@ -222,6 +350,24 @@ public class Building extends AbstractWorldObject {
|
||||
|
||||
writer.put((byte) 0); // 0
|
||||
writer.putFloat(3); // 3
|
||||
|
||||
// Live static props and their furniture: no own ID, no owner, flag clear, and no health on furniture.
|
||||
if (building.isStaticWorldObject()) {
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
writer.putInt(0);
|
||||
|
||||
writer.put((byte) 0);
|
||||
|
||||
if (building.parentBuildingID != 0) {
|
||||
writer.putFloat(0);
|
||||
writer.putFloat(0);
|
||||
} else {
|
||||
writer.putFloat(building.health.get());
|
||||
writer.putFloat(building.healthMax);
|
||||
}
|
||||
|
||||
} else {
|
||||
writer.putInt(GameObjectType.Building.ordinal());
|
||||
writer.putInt(building.getObjectUUID());
|
||||
|
||||
@@ -235,6 +381,7 @@ public class Building extends AbstractWorldObject {
|
||||
writer.put((byte) 1); // End Datablock
|
||||
writer.putFloat(building.health.get());
|
||||
writer.putFloat(building.healthMax);
|
||||
}
|
||||
|
||||
if (building.blueprintUUID == 0)
|
||||
writer.putInt(0);
|
||||
@@ -689,14 +836,6 @@ public class Building extends AbstractWorldObject {
|
||||
this.healthMax = maxHealth;
|
||||
}
|
||||
|
||||
public final void setw(float value) {
|
||||
this.w = value;
|
||||
}
|
||||
|
||||
public final float getw() {
|
||||
return this.w;
|
||||
}
|
||||
|
||||
public final Vector3f getMeshScale() {
|
||||
return this.meshScale;
|
||||
}
|
||||
@@ -945,7 +1084,9 @@ public class Building extends AbstractWorldObject {
|
||||
|
||||
resists = new Resists("Building");
|
||||
|
||||
if (this.parentZone != null) {
|
||||
// Static buildings got their world location and NPC owner from createStaticBuilding.
|
||||
|
||||
if (this.parentZone != null && !this.isStaticWorldObject()) {
|
||||
if (this.parentBuildingID != 0) {
|
||||
Building parentBuilding = BuildingManager.getBuilding(this.parentBuildingID);
|
||||
if (parentBuilding != null) {
|
||||
@@ -964,15 +1105,11 @@ public class Building extends AbstractWorldObject {
|
||||
}
|
||||
}
|
||||
|
||||
// Submit upgrade job if building is currently set to rank.
|
||||
// City load reads ownerIsNPC before NPCs are cached, so boot uses the owner set read ahead of buildings.
|
||||
|
||||
try {
|
||||
DbObjectType objectType = DbManager.BuildingQueries.GET_UID_ENUM(this.ownerUUID);
|
||||
this.ownerIsNPC = (objectType == DbObjectType.NPC);
|
||||
} catch (Exception e) {
|
||||
this.ownerIsNPC = false;
|
||||
Logger.error("Failed to find Object Type for owner " + this.ownerUUID + " Location " + this.getLoc().toString());
|
||||
}
|
||||
if (!this.isStaticWorldObject())
|
||||
this.ownerIsNPC = BuildingManager._npcBuildingOwners.contains(this.ownerUUID)
|
||||
|| DbManager.inCache(GameObjectType.NPC, this.ownerUUID);
|
||||
|
||||
// Reference friend and condemn lists from BuildingManager
|
||||
|
||||
|
||||
@@ -141,6 +141,43 @@ public class City extends AbstractWorldObject {
|
||||
|
||||
}
|
||||
|
||||
public City() {
|
||||
super();
|
||||
}
|
||||
|
||||
// NPC city of a seed guild (World Boot Plan 2.5). Fields the JSON lacks take the values every DB NPC city row had.
|
||||
public static City createStaticCity(int cityID, Zone parentZone, Building treeOfLife, boolean noobIsle) {
|
||||
|
||||
City city = new City();
|
||||
|
||||
city.setObjectUUID(cityID);
|
||||
city.setStaticWorldObject(true);
|
||||
city.gridObjectType = GridObjectType.STATIC;
|
||||
city.parentZoneUUID = parentZone.getObjectUUID();
|
||||
city.cityName = parentZone.zoneName;
|
||||
city.motto = "";
|
||||
city.description = "";
|
||||
city.isNpc = 1;
|
||||
city.isSafeHold = 1;
|
||||
city.isNoobIsle = noobIsle ? 1 : 0;
|
||||
city.established = LocalDateTime.of(2003, 3, 25, 9, 0);
|
||||
city.realmTaxDate = LocalDateTime.now();
|
||||
city.treeOfLifeID = treeOfLife.getObjectUUID();
|
||||
city.bindX = 0;
|
||||
city.bindZ = 0;
|
||||
city.bindLoc = Vector3fImmutable.ZERO;
|
||||
city.radiusType = 1;
|
||||
|
||||
// DB rows held 32, and the ResultSet constructor takes 2 off
|
||||
city.bindRadius = 30;
|
||||
|
||||
city.forceRename = false;
|
||||
city.open = false;
|
||||
city.hash = DataWarehouse.hasher.encryptObjectID(cityID);
|
||||
|
||||
return city;
|
||||
}
|
||||
|
||||
/*
|
||||
* Utils
|
||||
*/
|
||||
@@ -281,7 +318,7 @@ public class City extends AbstractWorldObject {
|
||||
city = City.getCity(cityID);
|
||||
|
||||
if (city == null)
|
||||
return mbEnums.Ruins.getRandomRuin().getLocation();
|
||||
return ZoneManager.getRandomRuinLocation();
|
||||
|
||||
return city.getBindLoc();
|
||||
}
|
||||
@@ -1279,6 +1316,30 @@ public class City extends AbstractWorldObject {
|
||||
return forceRename;
|
||||
}
|
||||
|
||||
public int getTreeOfLifeID() {
|
||||
return treeOfLifeID;
|
||||
}
|
||||
|
||||
public boolean isNpc() {
|
||||
return isNpc == 1;
|
||||
}
|
||||
|
||||
public float getBindX() {
|
||||
return bindX;
|
||||
}
|
||||
|
||||
public float getBindZ() {
|
||||
return bindZ;
|
||||
}
|
||||
|
||||
public float getBindRadius() {
|
||||
return bindRadius;
|
||||
}
|
||||
|
||||
public int getRadiusType() {
|
||||
return radiusType;
|
||||
}
|
||||
|
||||
public void setForceRename(boolean forceRename) {
|
||||
if (!DbManager.CityQueries.updateforceRename(this, forceRename))
|
||||
return;
|
||||
|
||||
@@ -21,6 +21,7 @@ import engine.net.client.msg.AllianceChangeMsg;
|
||||
import engine.net.client.msg.ErrorPopupMsg;
|
||||
import engine.net.client.msg.UpdateClientAlliancesMsg;
|
||||
import engine.net.client.msg.guild.GuildInfoMsg;
|
||||
import engine.parsers.objects.GuildSeedEntry;
|
||||
import engine.server.world.WorldServer;
|
||||
import org.joda.time.DateTime;
|
||||
import org.pmw.tinylog.Logger;
|
||||
@@ -35,15 +36,15 @@ public class Guild extends AbstractWorldObject {
|
||||
|
||||
private static Guild errantGuild;
|
||||
private static Guild errantNation;
|
||||
private final String name;
|
||||
private final GuildTag guildTag;
|
||||
public final GuildCharterType charter;
|
||||
private final String leadershipType; // Have to see how this is sent to the client
|
||||
private final int repledgeMin;
|
||||
private final int repledgeMax;
|
||||
private final int repledgeKick;
|
||||
private final int teleportMin;
|
||||
private final int teleportMax;
|
||||
private String name;
|
||||
private GuildTag guildTag;
|
||||
public GuildCharterType charter;
|
||||
private String leadershipType; // Have to see how this is sent to the client
|
||||
private int repledgeMin;
|
||||
private int repledgeMax;
|
||||
private int repledgeKick;
|
||||
private int teleportMin;
|
||||
private int teleportMax;
|
||||
private final ConcurrentHashMap<Integer, Condemned> guildCondemned = new ConcurrentHashMap<>();
|
||||
public boolean wooWasModified;
|
||||
public HashMap<Integer, GuildAlliances> guildAlliances = new HashMap<>();
|
||||
@@ -67,6 +68,13 @@ public class Guild extends AbstractWorldObject {
|
||||
private String hash;
|
||||
private boolean ownerIsNPC;
|
||||
|
||||
/**
|
||||
* No-arg Constructor
|
||||
*/
|
||||
private Guild() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* No Id Constructor
|
||||
*/
|
||||
@@ -163,6 +171,33 @@ public class Guild extends AbstractWorldObject {
|
||||
this.hash = rs.getString("hash");
|
||||
}
|
||||
|
||||
// Every NPC guild is its own nation, as live. No warehouse hash until there is one that accepts negative IDs.
|
||||
public static Guild createStaticGuild(GuildSeedEntry seedEntry) {
|
||||
|
||||
Guild guild = new Guild();
|
||||
|
||||
guild.setObjectUUID(DbManager.nextStaticID(GameObjectType.Guild,
|
||||
"guild:" + Integer.toUnsignedString(seedEntry.guildKeys.get(0))));
|
||||
guild.setStaticWorldObject(true);
|
||||
guild.name = seedEntry.name;
|
||||
guild.charter = seedEntry.charter;
|
||||
guild.leadershipType = seedEntry.leadershipType;
|
||||
guild.guildTag = new GuildTag(seedEntry.crest[0], seedEntry.crest[1], seedEntry.crest[2],
|
||||
seedEntry.crest[4], seedEntry.crest[3]);
|
||||
guild.motto = seedEntry.motto;
|
||||
guild.repledgeMin = seedEntry.repledgeMin;
|
||||
guild.repledgeMax = seedEntry.repledgeMax;
|
||||
guild.repledgeKick = seedEntry.repledgeKick;
|
||||
guild.teleportMin = seedEntry.teleportMin;
|
||||
guild.teleportMax = seedEntry.teleportMax;
|
||||
guild.banishList = new ArrayList<>();
|
||||
guild.subGuildList = new ArrayList<>();
|
||||
guild.nation = guild;
|
||||
guild.guildState = GuildManager.calcuateGuildState(guild);
|
||||
|
||||
return guild;
|
||||
}
|
||||
|
||||
public static AbstractCharacter GetGL(Guild guild) {
|
||||
if (guild == null)
|
||||
return null;
|
||||
|
||||
+10
-12
@@ -15,6 +15,8 @@ import engine.job.JobScheduler;
|
||||
import engine.jobs.DeferredPowerJob;
|
||||
import engine.jobs.UpgradeNPCJob;
|
||||
import engine.math.Bounds;
|
||||
import engine.math.FastMath;
|
||||
import engine.math.Quaternion;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import engine.mbEnums.*;
|
||||
@@ -42,7 +44,6 @@ import static java.lang.Math.toIntExact;
|
||||
|
||||
public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
|
||||
private static int staticID = 0;
|
||||
//mob specific
|
||||
public final ConcurrentHashMap<Integer, Float> playerAgroMap = new ConcurrentHashMap<>(); //key = Player value = hate value
|
||||
|
||||
@@ -80,6 +81,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
// PERSIST.
|
||||
public Mob() {
|
||||
super();
|
||||
this.setRotation(Quaternion.fromYaw(FastMath.PI));
|
||||
this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
||||
this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
||||
this.bindLoc = Vector3fImmutable.ZERO;
|
||||
@@ -99,6 +101,9 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
float statAlt;
|
||||
float statLon;
|
||||
|
||||
// mob_rotation is not loaded: the old default (cos θ/2 = 0) faced every mob at yaw π.
|
||||
this.setRotation(Quaternion.fromYaw(FastMath.PI));
|
||||
|
||||
try {
|
||||
this.dbID = rs.getInt(1);
|
||||
this.loadID = rs.getInt("mob_mobbaseID");
|
||||
@@ -240,14 +245,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
|
||||
writer.putVector3f(mob.getLoc());
|
||||
|
||||
//Rotation
|
||||
float radians = (float) Math.acos(mob.getRot().y) * 2;
|
||||
|
||||
if (mob.building != null)
|
||||
if (mob.building.getBounds() != null && mob.building.getBounds().getQuaternion() != null)
|
||||
radians += (mob.building.getBounds().getQuaternion()).angleY;
|
||||
|
||||
writer.putFloat(radians);
|
||||
writer.putFloat(mob.getClientYaw());
|
||||
|
||||
//Inventory Stuff
|
||||
|
||||
@@ -467,7 +465,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
Mob minionMobile;
|
||||
|
||||
minionMobile = new Mob();
|
||||
minionMobile.currentID = (--Mob.staticID);
|
||||
minionMobile.currentID = ItemManager.lastNegativeID.decrementAndGet();
|
||||
|
||||
minionMobile.level = level;
|
||||
minionMobile.loadID = guardCaptain.loadID;
|
||||
@@ -528,7 +526,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
Mob siegeMinion;
|
||||
|
||||
siegeMinion = new Mob();
|
||||
siegeMinion.currentID = (--Mob.staticID);
|
||||
siegeMinion.currentID = ItemManager.lastNegativeID.decrementAndGet();
|
||||
|
||||
siegeMinion.level = 1;
|
||||
siegeMinion.loadID = loadID;
|
||||
@@ -560,7 +558,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
|
||||
Mob petMinion = new Mob();
|
||||
|
||||
petMinion.currentID = (--Mob.staticID);
|
||||
petMinion.currentID = ItemManager.lastNegativeID.decrementAndGet();
|
||||
|
||||
petMinion.level = (short) (level + 20);
|
||||
petMinion.loadID = loadID;
|
||||
|
||||
@@ -15,7 +15,8 @@ import engine.job.JobContainer;
|
||||
import engine.job.JobScheduler;
|
||||
import engine.jobs.UpgradeNPCJob;
|
||||
import engine.math.Bounds;
|
||||
import engine.math.Vector3f;
|
||||
import engine.math.FastMath;
|
||||
import engine.math.Quaternion;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import engine.mbEnums.*;
|
||||
@@ -80,6 +81,7 @@ public class NPC extends AbstractCharacter {
|
||||
public NPC() {
|
||||
|
||||
super();
|
||||
this.setRotation(Quaternion.fromYaw(FastMath.PI));
|
||||
this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
||||
this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
||||
}
|
||||
@@ -118,7 +120,8 @@ public class NPC extends AbstractCharacter {
|
||||
this.buyPercent = .33f;
|
||||
this.sellPercent = 1;
|
||||
|
||||
this.setRot(new Vector3f(0, rs.getFloat("npc_rotation"), 0));
|
||||
// npc_rotation holds cos θ/2
|
||||
this.setRotation(Quaternion.fromYaw((float) Math.acos(rs.getFloat("npc_rotation")) * 2));
|
||||
|
||||
this.statLat = rs.getFloat("npc_spawnX");
|
||||
this.statAlt = rs.getFloat("npc_spawnY");
|
||||
@@ -320,14 +323,7 @@ public class NPC extends AbstractCharacter {
|
||||
else
|
||||
writer.putVector3f(npc.getLoc());
|
||||
|
||||
//Rotation
|
||||
float radians = (float) Math.acos(npc.getRot().y) * 2;
|
||||
|
||||
if (npc.building != null)
|
||||
if (npc.building.getBounds() != null && npc.building.getBounds().getQuaternion() != null)
|
||||
radians += (npc.building.getBounds().getQuaternion()).angleY;
|
||||
|
||||
writer.putFloat(radians);
|
||||
writer.putFloat(npc.getClientYaw());
|
||||
|
||||
//Running Speed
|
||||
writer.putInt(0);
|
||||
|
||||
@@ -2043,11 +2043,11 @@ public class PlayerCharacter extends AbstractCharacter {
|
||||
this.stopMovement(this.getBindLoc());
|
||||
} else if (bindBuilding != null) {
|
||||
if (bindBuilding.getParentZone().equals(ZoneManager.findSmallestZone(this.getLoc())))
|
||||
this.teleport(Ruins.getRandomRuin().getLocation());
|
||||
this.teleport(ZoneManager.getRandomRuinLocation());
|
||||
else
|
||||
this.teleport(this.getBindLoc());
|
||||
} else // no bind building found for player, teleport to ruins.
|
||||
this.teleport(Ruins.getRandomRuin().getLocation());
|
||||
this.teleport(ZoneManager.getRandomRuinLocation());
|
||||
|
||||
this.lastUpdateTime = System.currentTimeMillis();
|
||||
this.lastStamUpdateTime = System.currentTimeMillis();
|
||||
@@ -2144,14 +2144,14 @@ public class PlayerCharacter extends AbstractCharacter {
|
||||
//handle rented room binds.
|
||||
|
||||
if (bindBuilding == null) {
|
||||
bindLocation = mbEnums.Ruins.getRandomRuin().getLocation();
|
||||
bindLocation = ZoneManager.getRandomRuinLocation();
|
||||
return bindLocation;
|
||||
}
|
||||
|
||||
bindLocation = BuildingManager.GetBindLocationForBuilding(bindBuilding);
|
||||
|
||||
if (bindLocation == null)
|
||||
bindLocation = mbEnums.Ruins.getRandomRuin().getLocation();
|
||||
bindLocation = ZoneManager.getRandomRuinLocation();
|
||||
|
||||
return bindLocation;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ import engine.math.Vector2f;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import engine.net.ByteBufferWriter;
|
||||
import engine.parsers.objects.CZoneTemplate;
|
||||
import engine.parsers.objects.ZoneTreeEntry;
|
||||
import engine.server.MBServerStatics;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
@@ -33,20 +35,24 @@ public class Zone extends AbstractWorldObject {
|
||||
public final Set<NPC> zoneNPCSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
public final Set<Mob> zoneMobSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
public ZoneTemplate template;
|
||||
// Server-only hotzone range for zones with icons; no client data carries zone levels
|
||||
public static final int MACRO_ZONE_MIN_LEVEL = 30;
|
||||
public static final int MACRO_ZONE_MAX_LEVEL = 60;
|
||||
|
||||
public final int playerCityUUID;
|
||||
public final String zoneName;
|
||||
public CZoneTemplate template;
|
||||
|
||||
public int playerCityUUID;
|
||||
public String zoneName;
|
||||
public float major_radius;
|
||||
public float minor_radius;
|
||||
public final float xOffset;
|
||||
public final float zOffset;
|
||||
public final float yOffset;
|
||||
public final int templateID;
|
||||
public final byte peace_zone;
|
||||
public final String icon1;
|
||||
public final String icon2;
|
||||
public final String icon3;
|
||||
public float xOffset;
|
||||
public float zOffset;
|
||||
public float yOffset;
|
||||
public int templateID;
|
||||
public byte peace_zone;
|
||||
public String icon1;
|
||||
public String icon2;
|
||||
public String icon3;
|
||||
public float absX = 0.0f;
|
||||
public float absY = 0.0f;
|
||||
public float absZ = 0.0f;
|
||||
@@ -65,6 +71,13 @@ public class Zone extends AbstractWorldObject {
|
||||
|
||||
public Terrain terrain = null;
|
||||
|
||||
/**
|
||||
* No Id Constructor
|
||||
*/
|
||||
public Zone() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* ResultSet Constructor
|
||||
*/
|
||||
@@ -97,6 +110,36 @@ public class Zone extends AbstractWorldObject {
|
||||
|
||||
}
|
||||
|
||||
// zoneID is the static ID, or the tree UID while static buildings still load from the DB (W10)
|
||||
public static Zone createStaticZone(ZoneTreeEntry treeEntry, int zoneID, int parentZoneID) {
|
||||
|
||||
Zone zone = new Zone();
|
||||
|
||||
zone.setObjectUUID(zoneID);
|
||||
zone.setStaticWorldObject(true);
|
||||
zone.parentZoneID = parentZoneID;
|
||||
zone.templateID = treeEntry.template;
|
||||
zone.zoneName = treeEntry.name;
|
||||
zone.peace_zone = treeEntry.peaceZone ? (byte) 1 : (byte) 0;
|
||||
zone.xOffset = treeEntry.offset.x;
|
||||
zone.yOffset = treeEntry.offset.y;
|
||||
zone.zOffset = treeEntry.offset.z;
|
||||
zone.playerCityUUID = 0;
|
||||
zone.guild_zone = false;
|
||||
zone.icon1 = treeEntry.terrain;
|
||||
zone.icon2 = treeEntry.biome;
|
||||
zone.icon3 = treeEntry.height;
|
||||
|
||||
if (!zone.icon1.isEmpty()) {
|
||||
zone.min_level = MACRO_ZONE_MIN_LEVEL;
|
||||
zone.max_level = MACRO_ZONE_MAX_LEVEL;
|
||||
}
|
||||
|
||||
zone.hash = DataWarehouse.hasher.encryptObjectID(zone.getObjectUUID());
|
||||
|
||||
return zone;
|
||||
}
|
||||
|
||||
public static void serializeForClientMsg(Zone zone, ByteBufferWriter writer) {
|
||||
|
||||
if (zone.templateID == 0 && zone.playerCityUUID == 0)
|
||||
@@ -168,14 +211,14 @@ public class Zone extends AbstractWorldObject {
|
||||
this.minor_radius = mbEnums.CityBoundsType.ZONE.halfExtents;
|
||||
this.major_radius = mbEnums.CityBoundsType.ZONE.halfExtents;
|
||||
} else {
|
||||
this.minor_radius = this.template.minor_radius;
|
||||
this.major_radius = this.template.major_radius;
|
||||
this.minor_radius = this.template.zone_minor_radius;
|
||||
this.major_radius = this.template.zone_major_radius;
|
||||
}
|
||||
|
||||
this.setParent();
|
||||
this.setBounds();
|
||||
|
||||
if (this.template.terrain_type.equals("NONE"))
|
||||
if (this.template.terrain_type == mbEnums.TerrainGenType.NONE)
|
||||
this.terrain = null;
|
||||
else
|
||||
this.terrain = new Terrain(this);
|
||||
@@ -244,15 +287,15 @@ public class Zone extends AbstractWorldObject {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.template.sea_level_type) {
|
||||
case "WORLD":
|
||||
this.sea_level = world_sea_level + this.template.sea_level;
|
||||
switch (this.template.zone_sea_level_type) {
|
||||
case WORLD:
|
||||
this.sea_level = world_sea_level + this.template.zone_sea_level;
|
||||
break;
|
||||
case "PARENT":
|
||||
this.sea_level = this.parent.sea_level + this.template.sea_level;
|
||||
case PARENT:
|
||||
this.sea_level = this.parent.sea_level + this.template.zone_sea_level;
|
||||
break;
|
||||
case "SELF":
|
||||
this.sea_level = this.global_height + this.template.sea_level;
|
||||
case SELF:
|
||||
this.sea_level = this.global_height + this.template.zone_sea_level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
|
||||
package engine.objects;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class ZoneTemplate {
|
||||
|
||||
public int templateID;
|
||||
public String zone_type;
|
||||
public String zone_name;
|
||||
public String peace_zone;
|
||||
|
||||
public float major_radius;
|
||||
public float minor_radius;
|
||||
public float min_blend;
|
||||
public float max_blend;
|
||||
public String has_water;
|
||||
public String sea_level_type;
|
||||
public float sea_level;
|
||||
public String has_terrain;
|
||||
public String terrain_type;
|
||||
public float terrain_max_y;
|
||||
public int terrain_image;
|
||||
|
||||
/**
|
||||
* ResultSet Constructor
|
||||
*/
|
||||
|
||||
public ZoneTemplate() {
|
||||
|
||||
}
|
||||
|
||||
public ZoneTemplate(ResultSet rs) throws SQLException {
|
||||
this.templateID = rs.getInt("template");
|
||||
this.zone_type = rs.getString("zone_type");
|
||||
this.zone_name = rs.getString("zone_name");
|
||||
this.major_radius = rs.getFloat("zone_major_radius");
|
||||
this.minor_radius = rs.getFloat("zone_minor_radius");
|
||||
this.min_blend = rs.getFloat("zone_min_blend");
|
||||
this.max_blend = rs.getFloat("zone_max_blend");
|
||||
this.has_water = rs.getString("zone_has_water");
|
||||
this.sea_level_type = rs.getString("zone_sea_level_type");
|
||||
this.sea_level = rs.getFloat("zone_sea_level");
|
||||
this.has_terrain = rs.getString("zone_has_terrain_gen");
|
||||
this.terrain_type = rs.getString("terrain_type");
|
||||
this.terrain_max_y = rs.getFloat("terrain_max_y");
|
||||
this.terrain_image = rs.getInt("terrain_image");
|
||||
}
|
||||
|
||||
}
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.cache;
|
||||
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.mbEnums;
|
||||
import engine.parsers.objects.CZoneDrop;
|
||||
import engine.parsers.objects.CZoneMobile;
|
||||
import engine.parsers.objects.CZonePlacement;
|
||||
import engine.parsers.objects.CZonePointSet;
|
||||
import engine.parsers.objects.CZoneProp;
|
||||
import engine.parsers.objects.CZoneTemplate;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
||||
// Loads zone templates from the client cache JSON (CZONE/<id>.json), only those the map's zone tree uses.
|
||||
|
||||
public class ZoneParser {
|
||||
|
||||
private static final String zonePath = ConfigManager.DEFAULT_DATA_DIR + "CZONE/";
|
||||
|
||||
public static HashMap<Integer, CZoneTemplate> parseZoneTemplates(Collection<Integer> templateIds) {
|
||||
|
||||
HashMap<Integer, CZoneTemplate> zoneTemplates = new HashMap<>();
|
||||
File zoneFile;
|
||||
JSONObject zoneJson;
|
||||
int failedCount = 0;
|
||||
|
||||
for (int templateId : templateIds) {
|
||||
|
||||
zoneFile = new File(zonePath + templateId + ".json");
|
||||
|
||||
if (!zoneFile.exists()) {
|
||||
Logger.error("Zone template " + templateId + " has no CZONE file: " + zoneFile.getAbsolutePath());
|
||||
failedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
zoneJson = CacheParserUtil.readJsonFile(zoneFile);
|
||||
zoneTemplates.put(templateId, parseZoneEntry(templateId, zoneJson));
|
||||
} catch (Exception e) {
|
||||
Logger.error(zoneFile.getName() + ": " + e);
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Loaded " + zoneTemplates.size() + " of " + templateIds.size() + " zone templates");
|
||||
logCounts(zoneTemplates);
|
||||
|
||||
// Partial zone data is never usable; null tells the caller to abort
|
||||
if (failedCount > 0)
|
||||
return null;
|
||||
|
||||
return zoneTemplates;
|
||||
}
|
||||
|
||||
private static CZoneTemplate parseZoneEntry(int templateId, JSONObject zoneJson) {
|
||||
|
||||
CZoneTemplate zone = new CZoneTemplate();
|
||||
JSONObject terrainJson;
|
||||
JSONArray propsJson;
|
||||
JSONArray mobilesJson;
|
||||
JSONArray pointSetsJson;
|
||||
|
||||
zone.template_id = templateId;
|
||||
zone.zone_type = CacheParserUtil.readEnum(mbEnums.ZoneShapeType.class, zoneJson.getString("zone_type"), templateId, "zone_type");
|
||||
zone.zone_name = zoneJson.getString("zone_name");
|
||||
zone.zone_peace_zone = zoneJson.getBoolean("zone_peace_zone");
|
||||
zone.zone_major_radius = zoneJson.getFloat("zone_major_radius");
|
||||
zone.zone_minor_radius = zoneJson.getFloat("zone_minor_radius");
|
||||
zone.zone_min_blend = zoneJson.getFloat("zone_min_blend");
|
||||
zone.zone_max_blend = zoneJson.getFloat("zone_max_blend");
|
||||
zone.zone_sea_level = zoneJson.getFloat("zone_sea_level");
|
||||
zone.zone_sea_level_type = CacheParserUtil.readEnum(mbEnums.SeaLevelType.class, zoneJson.getString("zone_sea_level_type"),
|
||||
templateId, "zone_sea_level_type");
|
||||
zone.zone_has_water = zoneJson.getBoolean("zone_has_water");
|
||||
|
||||
zone.zone_has_terrain_gen = zoneJson.getBoolean("zone_has_terrain_gen");
|
||||
zone.terrain_type = mbEnums.TerrainGenType.NONE;
|
||||
|
||||
if (zone.zone_has_terrain_gen) {
|
||||
terrainJson = zoneJson.getJSONObject("zone_terrain_gen");
|
||||
zone.terrain_type = CacheParserUtil.readEnum(mbEnums.TerrainGenType.class, terrainJson.getString("terrain_type"),
|
||||
templateId, "terrain_type");
|
||||
|
||||
if (zone.terrain_type == mbEnums.TerrainGenType.TARGA) {
|
||||
zone.terrain_max_y = terrainJson.getFloat("terrain_max_y");
|
||||
zone.terrain_image = terrainJson.getInt("terrain_image");
|
||||
}
|
||||
}
|
||||
|
||||
propsJson = zoneJson.getJSONArray("zone_prop_info");
|
||||
|
||||
for (int i = 0; i < propsJson.length(); i++)
|
||||
zone.zone_prop_info.add(parseProp(propsJson.getJSONObject(i), templateId));
|
||||
|
||||
mobilesJson = zoneJson.getJSONArray("zone_mobile_info");
|
||||
|
||||
for (int i = 0; i < mobilesJson.length(); i++)
|
||||
zone.zone_mobile_info.add(parseMobile(mobilesJson.getJSONObject(i), templateId));
|
||||
|
||||
pointSetsJson = zoneJson.getJSONArray("zone_point_sets");
|
||||
|
||||
for (int i = 0; i < pointSetsJson.length(); i++)
|
||||
zone.zone_point_sets.add(parsePointSet(pointSetsJson.getJSONObject(i), templateId));
|
||||
|
||||
return zone;
|
||||
}
|
||||
|
||||
private static CZonePlacement parsePlacement(JSONObject placementJson, int templateId) {
|
||||
|
||||
CZonePlacement placement = new CZonePlacement();
|
||||
JSONObject sparseJson;
|
||||
|
||||
placement.basic_zone_spawn_location = CacheParserUtil.readVector3(placementJson.getJSONArray("basic_zone_spawn_location"));
|
||||
placement.basic_zone_name_override = placementJson.getString("basic_zone_name_override");
|
||||
placement.basic_zone_time_to_respawn = placementJson.getFloat("basic_zone_time_to_respawn");
|
||||
placement.basic_zone_spawn_radius = placementJson.getFloat("basic_zone_spawn_radius");
|
||||
placement.basic_zone_y_rot = placementJson.getFloat("basic_zone_y_rot");
|
||||
placement.basic_zone_template_id = (int) placementJson.getLong("basic_zone_template_id");
|
||||
placement.basic_zone_level_number = placementJson.getInt("basic_zone_level_number");
|
||||
placement.basic_zone_room_number = placementJson.getInt("basic_zone_room_number");
|
||||
|
||||
sparseJson = placementJson.getJSONObject("basic_zone_sparse_data");
|
||||
|
||||
for (String key : sparseJson.keySet()) {
|
||||
if (!key.equals("IMMUNE_TO_STEAL"))
|
||||
throw new IllegalArgumentException(templateId + ": basic_zone_sparse_data has unknown key '" + key + "'");
|
||||
|
||||
placement.basic_zone_immune_to_steal = sparseJson.getBoolean(key);
|
||||
}
|
||||
|
||||
return placement;
|
||||
}
|
||||
|
||||
private static CZoneProp parseProp(JSONObject propJson, int templateId) {
|
||||
|
||||
CZoneProp prop = new CZoneProp();
|
||||
JSONObject dataJson;
|
||||
JSONArray childrenJson;
|
||||
JSONArray mobilesJson;
|
||||
|
||||
prop.prop_type = CacheParserUtil.readEnum(mbEnums.ZonePropType.class, propJson.getString("prop_type"), templateId, "prop_type");
|
||||
|
||||
dataJson = propJson.getJSONObject("prop_data");
|
||||
prop.prop_basic_zone = parsePlacement(dataJson.getJSONObject("prop_basic_zone"), templateId);
|
||||
|
||||
childrenJson = dataJson.getJSONArray("prop_content_props");
|
||||
|
||||
for (int i = 0; i < childrenJson.length(); i++)
|
||||
prop.prop_content_props.add(parseProp(childrenJson.getJSONObject(i), templateId));
|
||||
|
||||
mobilesJson = dataJson.getJSONArray("prop_content_mobiles");
|
||||
|
||||
for (int i = 0; i < mobilesJson.length(); i++)
|
||||
prop.prop_content_mobiles.add(parseMobile(mobilesJson.getJSONObject(i), templateId));
|
||||
|
||||
prop.prop_registry = dataJson.getInt("prop_registry");
|
||||
|
||||
if (prop.prop_registry != 0)
|
||||
prop.prop_registry_name = dataJson.getString("prop_registry_name");
|
||||
|
||||
prop.prop_has_teleporter = dataJson.getBoolean("prop_has_teleporter");
|
||||
|
||||
if (prop.prop_has_teleporter)
|
||||
prop.prop_teleporter = (int) dataJson.getLong("prop_teleporter");
|
||||
|
||||
if (prop.prop_type == mbEnums.ZonePropType.CITY) {
|
||||
prop.city_prop_gm_guild = (int) dataJson.getLong("city_prop_gm_guild");
|
||||
prop.city_prop_gm_nation = (int) dataJson.getLong("city_prop_gm_nation");
|
||||
prop.city_prop_asset_rank = dataJson.getInt("city_prop_asset_rank");
|
||||
prop.city_prop_asset_funds = dataJson.getInt("city_prop_asset_funds");
|
||||
}
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
private static CZoneMobile parseMobile(JSONObject mobileJson, int templateId) {
|
||||
|
||||
CZoneMobile mobile = new CZoneMobile();
|
||||
JSONObject dataJson;
|
||||
JSONArray equipmentJson;
|
||||
JSONArray equipJson;
|
||||
JSONArray bootiesJson;
|
||||
JSONObject bootyJson;
|
||||
JSONArray itemsJson;
|
||||
String rootFsm;
|
||||
|
||||
mobile.mobile_type = CacheParserUtil.readEnum(mbEnums.ZoneMobileType.class, mobileJson.getString("mobile_type"), templateId, "mobile_type");
|
||||
|
||||
dataJson = mobileJson.getJSONObject("mobile_data");
|
||||
mobile.mobile_base_zone = parsePlacement(dataJson.getJSONObject("mobile_base_zone"), templateId);
|
||||
|
||||
mobile.mobile_number_in_group = dataJson.getInt("mobile_number_in_group");
|
||||
mobile.mobile_level = dataJson.getInt("mobile_level");
|
||||
mobile.mobile_dialog_override = (int) dataJson.getLong("mobile_dialog_override");
|
||||
mobile.mobile_rune_stone_ids.addAll(CacheParserUtil.readIntList(dataJson.getJSONArray("mobile_rune_stone_ids")));
|
||||
mobile.mobile_spawn_hps.addAll(CacheParserUtil.readIntList(dataJson.getJSONArray("mobile_spawn_hps")));
|
||||
|
||||
// Equipment entries are [item id, chance]
|
||||
|
||||
equipmentJson = dataJson.getJSONArray("mobile_equipment");
|
||||
|
||||
for (int i = 0; i < equipmentJson.length(); i++) {
|
||||
equipJson = equipmentJson.getJSONArray(i);
|
||||
mobile.mobile_equipment.add(new CZoneDrop((int) equipJson.getLong(0), equipJson.getFloat(1)));
|
||||
}
|
||||
|
||||
// Booty items are [chance, item id] pairs, the reverse of equipment
|
||||
|
||||
bootiesJson = dataJson.getJSONArray("mobile_booties");
|
||||
|
||||
for (int i = 0; i < bootiesJson.length(); i++) {
|
||||
bootyJson = bootiesJson.getJSONObject(i);
|
||||
|
||||
if (!bootyJson.getString("type").equals("ITEM"))
|
||||
throw new IllegalArgumentException(templateId + ": mobile_booties has unknown type '" + bootyJson.getString("type") + "'");
|
||||
|
||||
itemsJson = bootyJson.getJSONArray("items");
|
||||
|
||||
for (int j = 0; j + 1 < itemsJson.length(); j += 2)
|
||||
mobile.mobile_booties.add(new CZoneDrop((int) itemsJson.getLong(j + 1), itemsJson.getFloat(j)));
|
||||
}
|
||||
|
||||
rootFsm = dataJson.getString("mobile_root_fs_mid");
|
||||
|
||||
if (!rootFsm.isEmpty())
|
||||
mobile.mobile_root_fs_mid = CacheParserUtil.readEnum(mbEnums.MobBehaviourType.class, rootFsm, templateId, "mobile_root_fs_mid");
|
||||
|
||||
mobile.mobile_has_home_goal = dataJson.getBoolean("mobile_has_home_goal");
|
||||
|
||||
if (mobile.mobile_has_home_goal) {
|
||||
mobile.mobile_home_goal = (int) dataJson.getLong("mobile_home_goal");
|
||||
mobile.mobile_spawn_home_goal = dataJson.getBoolean("mobile_spawn_home_goal");
|
||||
}
|
||||
|
||||
mobile.mobile_has_target_goal = dataJson.getBoolean("mobile_has_target_goal");
|
||||
|
||||
if (mobile.mobile_has_target_goal) {
|
||||
mobile.mobile_target_goal = (int) dataJson.getLong("mobile_target_goal");
|
||||
mobile.mobile_spawn_target_goal = dataJson.getBoolean("mobile_spawn_target_goal");
|
||||
}
|
||||
|
||||
mobile.mobile_has_group_tactics = dataJson.getBoolean("mobile_has_group_tactics");
|
||||
|
||||
if (mobile.mobile_has_group_tactics)
|
||||
mobile.mobile_group_tactics = (int) dataJson.getLong("mobile_group_tactics");
|
||||
|
||||
mobile.mobile_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class,
|
||||
dataJson.getJSONArray("mobile_enemy_monster_types"), templateId, "mobile_enemy_monster_types"));
|
||||
mobile.mobile_not_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class,
|
||||
dataJson.getJSONArray("mobile_not_enemy_monster_types"), templateId, "mobile_not_enemy_monster_types"));
|
||||
mobile.mobile_friendly_charters.addAll(CacheParserUtil.readIntList(dataJson.getJSONArray("mobile_friendly_charters")));
|
||||
mobile.mobile_parley_name = dataJson.getString("mobile_parley_name");
|
||||
|
||||
if (mobile.mobile_type == mbEnums.ZoneMobileType.TRAINER) {
|
||||
mobile.trainer_std_profit_margin = dataJson.getFloat("trainer_std_profit_margin");
|
||||
mobile.trainer_profit_margin = dataJson.getFloat("trainer_profit_margin");
|
||||
}
|
||||
|
||||
return mobile;
|
||||
}
|
||||
|
||||
private static CZonePointSet parsePointSet(JSONObject pointSetJson, int templateId) {
|
||||
|
||||
CZonePointSet pointSet = new CZonePointSet();
|
||||
JSONArray pointsJson;
|
||||
JSONObject pointJson;
|
||||
CZonePointSet.Point point;
|
||||
|
||||
pointSet.zone_goal_type = CacheParserUtil.readEnum(mbEnums.PointSetType.class, pointSetJson.getString("zone_goal_type"),
|
||||
templateId, "zone_goal_type");
|
||||
pointSet.zone_goal_value = (int) pointSetJson.getLong("zone_goal_value");
|
||||
pointSet.zone_goal_name = pointSetJson.getString("zone_goal_name");
|
||||
|
||||
pointsJson = pointSetJson.getJSONArray("zone_goal_points");
|
||||
|
||||
for (int i = 0; i < pointsJson.length(); i++) {
|
||||
pointJson = pointsJson.getJSONObject(i);
|
||||
point = new CZonePointSet.Point();
|
||||
point.zone_goal_point_location = CacheParserUtil.readVector3(pointJson.getJSONArray("zone_goal_point_location"));
|
||||
point.zone_goal_point_vectors = CacheParserUtil.readIntArray(pointJson.getJSONArray("zone_goal_point_vectors"));
|
||||
pointSet.zone_goal_points.add(point);
|
||||
}
|
||||
|
||||
return pointSet;
|
||||
}
|
||||
|
||||
// Counts to check against World-Boot-Plan findings §1
|
||||
|
||||
private static void logCounts(HashMap<Integer, CZoneTemplate> zoneTemplates) {
|
||||
|
||||
int topProps = 0;
|
||||
int childProps = 0;
|
||||
int cityProps = 0;
|
||||
int zoneMobiles = 0;
|
||||
int zoneTrainers = 0;
|
||||
int propMobiles = 0;
|
||||
int propTrainers = 0;
|
||||
int pointSets = 0;
|
||||
|
||||
for (CZoneTemplate zone : zoneTemplates.values()) {
|
||||
topProps += zone.zone_prop_info.size();
|
||||
pointSets += zone.zone_point_sets.size();
|
||||
|
||||
for (CZoneMobile mobile : zone.zone_mobile_info) {
|
||||
if (mobile.mobile_type == mbEnums.ZoneMobileType.TRAINER)
|
||||
zoneTrainers++;
|
||||
else
|
||||
zoneMobiles++;
|
||||
}
|
||||
|
||||
for (CZoneProp prop : zone.zone_prop_info) {
|
||||
childProps += prop.prop_content_props.size();
|
||||
|
||||
if (prop.prop_type == mbEnums.ZonePropType.CITY)
|
||||
cityProps++;
|
||||
|
||||
for (CZoneMobile mobile : prop.prop_content_mobiles) {
|
||||
if (mobile.mobile_type == mbEnums.ZoneMobileType.TRAINER)
|
||||
propTrainers++;
|
||||
else
|
||||
propMobiles++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Zone props: " + topProps + " top level (" + cityProps + " CITY), " + childProps + " children");
|
||||
Logger.info("Zone mobiles: " + zoneMobiles + " MOBILE, " + zoneTrainers + " TRAINER; prop mobiles: "
|
||||
+ propMobiles + " MOBILE, " + propTrainers + " TRAINER; point sets: " + pointSets);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.map;
|
||||
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.mbEnums;
|
||||
import engine.parsers.cache.CacheParserUtil;
|
||||
import engine.parsers.objects.GuildSeed;
|
||||
import engine.parsers.objects.GuildSeedEntry;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
// Loads the NPC guild seed for a map (maps/<map>.guild), in file order. Guild keys are unsigned in the file.
|
||||
|
||||
public class GuildSeedParser {
|
||||
|
||||
private static final String mapPath = ConfigManager.DEFAULT_DATA_DIR + "maps/";
|
||||
|
||||
public static GuildSeed parseGuildSeed(int mapID) {
|
||||
|
||||
GuildSeed guildSeed = new GuildSeed();
|
||||
File seedFile = new File(mapPath + mapID + ".guild");
|
||||
JSONObject seedJson;
|
||||
JSONArray guildsJson;
|
||||
JSONArray noGuildJson;
|
||||
JSONArray ruinJson;
|
||||
|
||||
if (!seedFile.exists()) {
|
||||
Logger.error("Guild seed not found: " + seedFile.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
seedJson = CacheParserUtil.readJsonFile(seedFile);
|
||||
|
||||
if (seedJson.getInt("map") != mapID) {
|
||||
Logger.error(seedFile.getName() + ": map " + seedJson.getInt("map") + " does not match MB_WORLD_MAPID " + mapID);
|
||||
return null;
|
||||
}
|
||||
|
||||
guildsJson = seedJson.getJSONArray("guilds");
|
||||
|
||||
for (int i = 0; i < guildsJson.length(); i++)
|
||||
guildSeed.guilds.add(parseGuildEntry(guildsJson.getJSONObject(i)));
|
||||
|
||||
noGuildJson = seedJson.getJSONArray("noGuildKeys");
|
||||
|
||||
for (int i = 0; i < noGuildJson.length(); i++)
|
||||
guildSeed.noGuildKeys.add((int) noGuildJson.getLong(i));
|
||||
|
||||
ruinJson = seedJson.getJSONArray("ruinKeys");
|
||||
|
||||
for (int i = 0; i < ruinJson.length(); i++)
|
||||
guildSeed.ruinKeys.add((int) ruinJson.getLong(i));
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(seedFile.getName() + ": " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.info("Loaded " + guildSeed.guilds.size() + " guilds from " + seedFile.getName());
|
||||
|
||||
return guildSeed;
|
||||
}
|
||||
|
||||
private static GuildSeedEntry parseGuildEntry(JSONObject guildJson) {
|
||||
|
||||
GuildSeedEntry guild = new GuildSeedEntry();
|
||||
JSONArray keysJson = guildJson.getJSONArray("guildKeys");
|
||||
JSONArray crestJson = guildJson.getJSONArray("crest");
|
||||
|
||||
for (int i = 0; i < keysJson.length(); i++)
|
||||
guild.guildKeys.add((int) keysJson.getLong(i));
|
||||
|
||||
guild.nationKey = (int) guildJson.getLong("nationKey");
|
||||
guild.name = guildJson.getString("name");
|
||||
guild.charter = mbEnums.GuildCharterType.valueOf(guildJson.getString("charter"));
|
||||
guild.leadershipType = guildJson.getString("leadershipType");
|
||||
guild.motto = guildJson.getString("motto");
|
||||
guild.repledgeMin = guildJson.getInt("repledgeMin");
|
||||
guild.repledgeMax = guildJson.getInt("repledgeMax");
|
||||
guild.repledgeKick = guildJson.getInt("repledgeKick");
|
||||
guild.teleportMin = guildJson.getInt("teleportMin");
|
||||
guild.teleportMax = guildJson.getInt("teleportMax");
|
||||
guild.noobIsle = guildJson.optBoolean("noobIsle", false);
|
||||
|
||||
if (guild.guildKeys.isEmpty())
|
||||
throw new IllegalStateException("guild " + guild.name + " has no guild keys");
|
||||
|
||||
if (crestJson.length() != 5)
|
||||
throw new IllegalStateException("guild " + guild.name + " crest has " + crestJson.length() + " values");
|
||||
|
||||
guild.crest = new int[5];
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
guild.crest[i] = crestJson.getInt(i);
|
||||
|
||||
return guild;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.map;
|
||||
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.parsers.cache.CacheParserUtil;
|
||||
import engine.parsers.objects.ZoneTreeEntry;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
// Loads the zone tree for a map (maps/<map>.tree), in file order: parents always precede their children.
|
||||
|
||||
public class ZoneTreeParser {
|
||||
|
||||
private static final String mapPath = ConfigManager.DEFAULT_DATA_DIR + "maps/";
|
||||
|
||||
public static ArrayList<ZoneTreeEntry> parseZoneTree(int mapID) {
|
||||
|
||||
ArrayList<ZoneTreeEntry> zoneTree = new ArrayList<>();
|
||||
File treeFile = new File(mapPath + mapID + ".tree");
|
||||
JSONObject treeJson;
|
||||
JSONArray zonesJson;
|
||||
|
||||
if (!treeFile.exists()) {
|
||||
Logger.error("Zone tree not found: " + treeFile.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
treeJson = CacheParserUtil.readJsonFile(treeFile);
|
||||
|
||||
if (treeJson.getInt("map") != mapID) {
|
||||
Logger.error(treeFile.getName() + ": map " + treeJson.getInt("map") + " does not match MB_WORLD_MAPID " + mapID);
|
||||
return null;
|
||||
}
|
||||
|
||||
zonesJson = treeJson.getJSONArray("zones");
|
||||
|
||||
for (int i = 0; i < zonesJson.length(); i++)
|
||||
zoneTree.add(parseZoneEntry(zonesJson.getJSONObject(i)));
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(treeFile.getName() + ": " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.info("Loaded " + zoneTree.size() + " zones from " + treeFile.getName());
|
||||
|
||||
return zoneTree;
|
||||
}
|
||||
|
||||
private static ZoneTreeEntry parseZoneEntry(JSONObject zoneJson) {
|
||||
|
||||
ZoneTreeEntry zone = new ZoneTreeEntry();
|
||||
|
||||
zone.uid = zoneJson.getInt("uid");
|
||||
zone.parent = zoneJson.getInt("parent");
|
||||
zone.template = zoneJson.getInt("template");
|
||||
zone.name = zoneJson.getString("name");
|
||||
zone.offset = CacheParserUtil.readVector3(zoneJson.getJSONArray("offset"));
|
||||
zone.peaceZone = zoneJson.getBoolean("peaceZone");
|
||||
zone.terrain = zoneJson.getString("terrain");
|
||||
zone.biome = zoneJson.getString("biome");
|
||||
zone.height = zoneJson.getString("height");
|
||||
|
||||
// The server places zones by offset alone; a rotated zone would put everything in it out of place
|
||||
if (zoneJson.getFloat("yaw") != 0.0f)
|
||||
throw new IllegalStateException("zone " + zone.uid + " has a non-zero yaw");
|
||||
|
||||
if (zone.offset == null)
|
||||
throw new IllegalStateException("zone " + zone.uid + " has no offset");
|
||||
|
||||
return zone;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
// One item with a chance, from a CZONE mobile's mobile_equipment or mobile_booties.
|
||||
// Equipment chances are 0–1 fractions; booty chances are percentages (100.0, 50.0).
|
||||
|
||||
public class CZoneDrop {
|
||||
|
||||
public final int item_id;
|
||||
public final float chance;
|
||||
|
||||
public CZoneDrop(int item_id, float chance) {
|
||||
this.item_id = item_id;
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.mbEnums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
|
||||
// A MOBILE or TRAINER entry from a CZONE zone (zone_mobile_info) or from a
|
||||
// top-level prop (prop_content_mobiles). Field names match the JSON keys.
|
||||
// Fields that are default in every shipped file are not parsed (Task 1.1 list).
|
||||
|
||||
public class CZoneMobile {
|
||||
|
||||
public mbEnums.ZoneMobileType mobile_type;
|
||||
public CZonePlacement mobile_base_zone;
|
||||
|
||||
public int mobile_number_in_group;
|
||||
public int mobile_level;
|
||||
public int mobile_dialog_override;
|
||||
public final ArrayList<Integer> mobile_rune_stone_ids = new ArrayList<>();
|
||||
public final ArrayList<CZoneDrop> mobile_equipment = new ArrayList<>();
|
||||
public final ArrayList<CZoneDrop> mobile_booties = new ArrayList<>();
|
||||
public final ArrayList<Integer> mobile_spawn_hps = new ArrayList<>();
|
||||
|
||||
// null when the entry has no root FSM
|
||||
|
||||
public mbEnums.MobBehaviourType mobile_root_fs_mid;
|
||||
|
||||
// Goal values are zone_point_sets zone_goal_value hashes
|
||||
|
||||
public boolean mobile_has_home_goal;
|
||||
public int mobile_home_goal;
|
||||
public boolean mobile_spawn_home_goal;
|
||||
public boolean mobile_has_target_goal;
|
||||
public int mobile_target_goal;
|
||||
public boolean mobile_spawn_target_goal;
|
||||
|
||||
public boolean mobile_has_group_tactics;
|
||||
public int mobile_group_tactics;
|
||||
|
||||
public final EnumSet<mbEnums.MonsterType> mobile_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
|
||||
public final EnumSet<mbEnums.MonsterType> mobile_not_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
|
||||
public final ArrayList<Integer> mobile_friendly_charters = new ArrayList<>();
|
||||
public String mobile_parley_name;
|
||||
|
||||
// TRAINER only
|
||||
|
||||
public float trainer_std_profit_margin;
|
||||
public float trainer_profit_margin;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.math.Vector3fImmutable;
|
||||
|
||||
// The basic_zone_* block shared by every CZONE prop and mobile
|
||||
// (client BasicZoneTemplateData). Field names match the JSON keys.
|
||||
// spawn_location is relative to the parent; y_rot is not the client yaw (W8: yaw = π − y_rot).
|
||||
|
||||
public class CZonePlacement {
|
||||
|
||||
public Vector3fImmutable basic_zone_spawn_location;
|
||||
public String basic_zone_name_override;
|
||||
public float basic_zone_time_to_respawn;
|
||||
public float basic_zone_spawn_radius;
|
||||
public float basic_zone_y_rot;
|
||||
public int basic_zone_template_id;
|
||||
public int basic_zone_level_number;
|
||||
public int basic_zone_room_number;
|
||||
|
||||
// IMMUNE_TO_STEAL is the only sparse key in the shipped cache
|
||||
|
||||
public boolean basic_zone_immune_to_steal;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// A zone_point_sets entry: a named path that mobile home/target goals point at
|
||||
// through zone_goal_value. Field names match the JSON keys.
|
||||
|
||||
public class CZonePointSet {
|
||||
|
||||
public mbEnums.PointSetType zone_goal_type;
|
||||
public int zone_goal_value;
|
||||
public String zone_goal_name;
|
||||
public final ArrayList<Point> zone_goal_points = new ArrayList<>();
|
||||
|
||||
public static class Point {
|
||||
|
||||
public Vector3fImmutable zone_goal_point_location;
|
||||
|
||||
// Six ints; the first is the point's index, the rest are unknown
|
||||
|
||||
public int[] zone_goal_point_vectors;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.mbEnums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// A PROP or CITY entry from a CZONE zone (zone_prop_info) or a child prop
|
||||
// (prop_content_props). Field names match the JSON keys.
|
||||
// Children never have children or mobiles in the shipped cache.
|
||||
|
||||
public class CZoneProp {
|
||||
|
||||
public mbEnums.ZonePropType prop_type;
|
||||
public CZonePlacement prop_basic_zone;
|
||||
|
||||
public final ArrayList<CZoneProp> prop_content_props = new ArrayList<>();
|
||||
public final ArrayList<CZoneMobile> prop_content_mobiles = new ArrayList<>();
|
||||
|
||||
public int prop_registry;
|
||||
public String prop_registry_name = "";
|
||||
public boolean prop_has_teleporter;
|
||||
public int prop_teleporter;
|
||||
|
||||
// CITY only. Guild and nation are keys into the map's guild seed (W7).
|
||||
|
||||
public int city_prop_gm_guild;
|
||||
public int city_prop_gm_nation;
|
||||
public int city_prop_asset_rank;
|
||||
public int city_prop_asset_funds;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.mbEnums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
// Zone template as defined by the client's cache data (MBDATA/data/cache/CZONE).
|
||||
// Field names match the JSON keys. Only server-needed fields are parsed (Task 1.1 list);
|
||||
// textures, water, weather, sound and fields default in every file are skipped.
|
||||
// A template is reused by many zones; placement lives in the zone tree.
|
||||
//
|
||||
// Read only once loaded.
|
||||
|
||||
public class CZoneTemplate {
|
||||
|
||||
public static HashMap<Integer, CZoneTemplate> _zones = new HashMap<>();
|
||||
|
||||
public int template_id;
|
||||
|
||||
public mbEnums.ZoneShapeType zone_type;
|
||||
public String zone_name;
|
||||
public boolean zone_peace_zone;
|
||||
public float zone_major_radius;
|
||||
public float zone_minor_radius;
|
||||
public float zone_min_blend;
|
||||
public float zone_max_blend;
|
||||
public float zone_sea_level;
|
||||
public mbEnums.SeaLevelType zone_sea_level_type;
|
||||
public boolean zone_has_water;
|
||||
|
||||
// zone_terrain_gen; max_y and image exist only on TARGA terrain
|
||||
|
||||
public boolean zone_has_terrain_gen;
|
||||
public mbEnums.TerrainGenType terrain_type;
|
||||
public float terrain_max_y;
|
||||
public int terrain_image;
|
||||
|
||||
public final ArrayList<CZoneProp> zone_prop_info = new ArrayList<>();
|
||||
public final ArrayList<CZoneMobile> zone_mobile_info = new ArrayList<>();
|
||||
public final ArrayList<CZonePointSet> zone_point_sets = new ArrayList<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
// A map's server-owned NPC guild seed (maps/<map>.guild). Guilds are in static ID order.
|
||||
// noGuildKeys are placeholder CITY-prop guild keys. ruinKeys mark the Ruins' trees: no guild either, and
|
||||
// their zones are where unbound characters respawn when MB_USE_RUINS is on.
|
||||
|
||||
public class GuildSeed {
|
||||
|
||||
public final ArrayList<GuildSeedEntry> guilds = new ArrayList<>();
|
||||
public final HashSet<Integer> noGuildKeys = new HashSet<>();
|
||||
public final HashSet<Integer> ruinKeys = new HashSet<>();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.mbEnums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// One NPC guild of a <map>.guild seed file. guildKeys and nationKey are CZONE city_prop_gm_guild / _nation values.
|
||||
// Crest is in obj_guild column order: backgroundColor01, backgroundColor02, symbolColor, backgroundDesign, symbol.
|
||||
|
||||
public class GuildSeedEntry {
|
||||
|
||||
public final ArrayList<Integer> guildKeys = new ArrayList<>();
|
||||
public int nationKey;
|
||||
public String name;
|
||||
public int[] crest;
|
||||
public mbEnums.GuildCharterType charter;
|
||||
public String leadershipType;
|
||||
public String motto;
|
||||
public int repledgeMin;
|
||||
public int repledgeMax;
|
||||
public int repledgeKick;
|
||||
public int teleportMin;
|
||||
public int teleportMax;
|
||||
public boolean noobIsle;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.math.Vector3fImmutable;
|
||||
|
||||
// One zone of a <map>.tree file, exported by mbScryer from a recording's NEWWORLD message.
|
||||
// Field names match the JSON keys; terrain, biome and height are the zone's three icons.
|
||||
|
||||
public class ZoneTreeEntry {
|
||||
|
||||
public int uid;
|
||||
public int parent;
|
||||
public int template;
|
||||
public String name;
|
||||
public Vector3fImmutable offset;
|
||||
public boolean peaceZone;
|
||||
public String terrain;
|
||||
public String biome;
|
||||
public String height;
|
||||
|
||||
}
|
||||
@@ -135,12 +135,12 @@ public class PowerActionParser {
|
||||
break;
|
||||
case SetItemFlag:
|
||||
powerAction.item_flag = header.enumToken(mbEnums.ItemFlags.class, 2);
|
||||
powerAction.item_flag_value = header.optionalBoolToken(3);
|
||||
powerAction.item_flag_value = header.optionalIntToken(3) != 0;
|
||||
break;
|
||||
case Track:
|
||||
powerAction.effect_ids.add(header.token(2));
|
||||
powerAction.track_player = header.boolToken(3);
|
||||
powerAction.track_corpse = header.boolToken(4);
|
||||
powerAction.track_player = header.intToken(3) != 0;
|
||||
powerAction.track_corpse = header.intToken(4) != 0;
|
||||
powerAction.track_filter = header.token(5);
|
||||
powerAction.max_track = header.intToken(7);
|
||||
break;
|
||||
@@ -175,7 +175,7 @@ public class PowerActionParser {
|
||||
powerAction.transfer_amount_curve = WpakParserUtil.curveToken(header, 5);
|
||||
powerAction.transfer_efficiency = header.floatToken(6);
|
||||
powerAction.transfer_efficiency_curve = WpakParserUtil.curveToken(header, 7);
|
||||
powerAction.transfer_target_to_caster = header.boolToken(8);
|
||||
powerAction.transfer_target_to_caster = header.intToken(8) != 0;
|
||||
powerAction.isaggressive = header.optionalIntToken(9) != 0;
|
||||
}
|
||||
|
||||
@@ -186,13 +186,13 @@ public class PowerActionParser {
|
||||
|
||||
switch (line.token(0)) {
|
||||
case "ISAGGRESSIVE=":
|
||||
powerAction.isaggressive = line.optionalBoolToken(1);
|
||||
powerAction.isaggressive = line.optionalExactTrueToken(1);
|
||||
break;
|
||||
case "BLADETRAILS=":
|
||||
powerAction.bladetrails = line.optionalBoolToken(1);
|
||||
powerAction.bladetrails = line.optionalExactTrueToken(1);
|
||||
break;
|
||||
case "ISRESISTABLE=":
|
||||
powerAction.isresistable = line.optionalBoolToken(1);
|
||||
powerAction.isresistable = line.optionalExactTrueToken(1);
|
||||
break;
|
||||
case "APPLYEFFECTSELF=":
|
||||
powerAction.applyeffectself = line.token(1);
|
||||
@@ -225,10 +225,10 @@ public class PowerActionParser {
|
||||
powerAction.femalebodyparts.addAll(line.intTokensFrom(1));
|
||||
break;
|
||||
case "SHOULDSHOWWEAPONS=":
|
||||
powerAction.shouldshowweapons = line.optionalBoolToken(1);
|
||||
powerAction.shouldshowweapons = line.optionalExactTrueToken(1);
|
||||
break;
|
||||
case "SHOULDSHOWARMOR=":
|
||||
powerAction.shouldshowarmor = line.optionalBoolToken(1);
|
||||
powerAction.shouldshowarmor = line.optionalExactTrueToken(1);
|
||||
break;
|
||||
case "ATTACKANIMS=":
|
||||
powerAction.attackanims.addAll(line.intTokensFrom(1));
|
||||
|
||||
@@ -20,8 +20,8 @@ import java.util.regex.Pattern;
|
||||
|
||||
public class WpakLine {
|
||||
|
||||
// The client reads numbers like C atoi/atof: the leading number counts, the rest is dropped
|
||||
// (Powers.cfg has "2.9.0" for a float and "10.0" for an int)
|
||||
// The client reads numbers with strtol/strtod (Core.dll String::ToInt/ToFloat): the leading
|
||||
// number counts, the rest is dropped, so "2.9.0" is 2.9 and "10.0" as an int is 10
|
||||
private static final Pattern intPrefix = Pattern.compile("[-+]?\\d+");
|
||||
private static final Pattern floatPrefix = Pattern.compile("[-+]?(\\d+\\.?\\d*|\\.\\d+)");
|
||||
|
||||
@@ -107,23 +107,44 @@ public class WpakLine {
|
||||
return intToken(index);
|
||||
}
|
||||
|
||||
// TRUE/FALSE, or a number where non-zero is true
|
||||
// Core.dll String::ToBool with the false default every wpak loader passes: a leading '0' is
|
||||
// false, '1'..'9' is true, then TRUE/FALSE in any case; anything else reads as false
|
||||
|
||||
public boolean boolToken(int index) {
|
||||
|
||||
String value = token(index);
|
||||
char first = value.isEmpty() ? 0 : value.charAt(0);
|
||||
|
||||
if (first == '0')
|
||||
return false;
|
||||
|
||||
if (first >= '1' && first <= '9')
|
||||
return true;
|
||||
|
||||
if (value.equalsIgnoreCase("TRUE"))
|
||||
return true;
|
||||
|
||||
if (value.equalsIgnoreCase("FALSE"))
|
||||
if (!value.equalsIgnoreCase("FALSE"))
|
||||
Logger.warn(location() + ": token " + index + " '" + value + "' read as false, as the client does");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some PowerActions keys compare the token to "TRUE" exactly (String_EqualsCStr 0x546450), so "1" is false
|
||||
|
||||
public boolean optionalExactTrueToken(int index) {
|
||||
|
||||
String value;
|
||||
|
||||
if (!has(index))
|
||||
return false;
|
||||
|
||||
try {
|
||||
return Integer.parseInt(value) != 0;
|
||||
} catch (NumberFormatException e) {
|
||||
throw error("token " + index + " '" + value + "' is not a boolean");
|
||||
}
|
||||
value = token(index);
|
||||
|
||||
if (!value.equals("TRUE") && !value.equals("FALSE"))
|
||||
Logger.warn(location() + ": token " + index + " '" + value + "' read as false, as the client does");
|
||||
|
||||
return value.equals("TRUE");
|
||||
}
|
||||
|
||||
public boolean optionalBoolToken(int index) {
|
||||
|
||||
@@ -36,6 +36,8 @@ import engine.parsers.cache.DeedParser;
|
||||
import engine.parsers.cache.ItemParser;
|
||||
import engine.parsers.cache.ParityCheck;
|
||||
import engine.parsers.cache.RuneParser;
|
||||
import engine.parsers.cache.ZoneParser;
|
||||
import engine.parsers.objects.CZoneTemplate;
|
||||
import engine.parsers.objects.DeedTemplate;
|
||||
import engine.parsers.objects.ItemTemplate;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
@@ -136,14 +138,20 @@ public class WorldServer {
|
||||
if (retVal != 0)
|
||||
Logger.error(
|
||||
".exec() returned value: '" + retVal);
|
||||
exit(retVal);
|
||||
exitServer(retVal);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(e.getMessage());
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
}
|
||||
|
||||
// The tinylog writing thread drops queued lines on System.exit; flush them so boot aborts are logged
|
||||
private static void exitServer(int status) {
|
||||
Configurator.shutdownWritingThread(true);
|
||||
exit(status);
|
||||
}
|
||||
|
||||
public static String getUptimeString() {
|
||||
|
||||
String outString = null;
|
||||
@@ -278,6 +286,13 @@ public class WorldServer {
|
||||
|
||||
private boolean init() {
|
||||
|
||||
mbEnums.StaticBuildingMode staticBuildingMode;
|
||||
int staticZoneRows;
|
||||
int npcCityRows = 0;
|
||||
int movedStaticIDs;
|
||||
ArrayList<StaticBuildingLoader.StaticBuildingEntry> staticBuildingEntries = null;
|
||||
ArrayList<StaticCityLoader.StaticCityEntry> staticCityEntries = null;
|
||||
|
||||
Logger.info("MAGICBANE SERVER GREETING:");
|
||||
Logger.info(ConfigManager.MB_WORLD_GREETING.getValue());
|
||||
|
||||
@@ -296,15 +311,12 @@ public class WorldServer {
|
||||
Logger.info("Initializing Errant Guild");
|
||||
Guild.getErrantGuild();
|
||||
|
||||
Logger.info("Loading zone template data");
|
||||
DbManager.ZoneQueries.LOAD_ALL_ZONE_TEMPLATES();
|
||||
|
||||
Logger.info("Loading Compound Curves");
|
||||
CompoundCurveTemplate._curves = CompoundCurveParser.parseCompoundCurvesFile();
|
||||
|
||||
if (CompoundCurveTemplate._curves == null || CompoundCurveTemplate._curves.isEmpty()) {
|
||||
Logger.error("ABORT! Compound curves failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
CurveRamp.verifyRampForms();
|
||||
@@ -314,7 +326,7 @@ public class WorldServer {
|
||||
|
||||
if (ModTypeTemplate._modTypes == null || ModTypeTemplate._modTypes.isEmpty()) {
|
||||
Logger.error("ABORT! Mod types failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Power Actions");
|
||||
@@ -322,7 +334,7 @@ public class WorldServer {
|
||||
|
||||
if (PowerActionTemplate._powerActions == null || PowerActionTemplate._powerActions.isEmpty()) {
|
||||
Logger.error("ABORT! Power actions failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Effects");
|
||||
@@ -330,7 +342,7 @@ public class WorldServer {
|
||||
|
||||
if (EffectTemplate._effects == null || EffectTemplate._effects.isEmpty()) {
|
||||
Logger.error("ABORT! Effects failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Powers");
|
||||
@@ -338,7 +350,7 @@ public class WorldServer {
|
||||
|
||||
if (PowerTemplate._powers == null || PowerTemplate._powers.isEmpty()) {
|
||||
Logger.error("ABORT! Powers failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Skills");
|
||||
@@ -346,7 +358,7 @@ public class WorldServer {
|
||||
|
||||
if (SkillTemplate._skills == null || SkillTemplate._skills.isEmpty()) {
|
||||
Logger.error("ABORT! Skills failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading SkillsBase");
|
||||
@@ -357,7 +369,7 @@ public class WorldServer {
|
||||
|
||||
if (StartingKitTemplate._kits == null || StartingKitTemplate._kits.isEmpty()) {
|
||||
Logger.error("ABORT! Starting kits failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Character Creation Runes");
|
||||
@@ -365,7 +377,7 @@ public class WorldServer {
|
||||
|
||||
if (CharCreateRuneList._runeList == null) {
|
||||
Logger.error("ABORT! Character creation runes failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Power Action Resource Costs");
|
||||
@@ -373,7 +385,7 @@ public class WorldServer {
|
||||
|
||||
if (PowActionCostTemplate._costs == null || PowActionCostTemplate._costs.isEmpty()) {
|
||||
Logger.error("ABORT! Power action resource costs failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Initializing PowersManager.");
|
||||
@@ -408,7 +420,7 @@ public class WorldServer {
|
||||
|
||||
if (DeedTemplate._deeds == null || DeedTemplate._deeds.isEmpty()) {
|
||||
Logger.error("ABORT! Deed templates failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Rune Templates");
|
||||
@@ -416,7 +428,7 @@ public class WorldServer {
|
||||
|
||||
if (RuneTemplate._runes == null || RuneTemplate._runes.isEmpty()) {
|
||||
Logger.error("ABORT! Rune templates failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Cache Item Templates");
|
||||
@@ -424,12 +436,29 @@ public class WorldServer {
|
||||
|
||||
if (ItemTemplate._items == null || ItemTemplate._items.isEmpty()) {
|
||||
Logger.error("ABORT! Cache item templates failed to load!");
|
||||
exit(1);
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Indexing Cache Templates");
|
||||
CObjectIndex.build();
|
||||
|
||||
Logger.info("Loading Zone Tree");
|
||||
|
||||
if (!ZoneManager.loadZoneTree(worldMapID)) {
|
||||
Logger.error("ABORT! Zone tree failed to load!");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading Cache Zone Templates");
|
||||
CZoneTemplate._zones = ZoneParser.parseZoneTemplates(ZoneManager.getZoneTreeTemplateIDs());
|
||||
|
||||
if (CZoneTemplate._zones == null || CZoneTemplate._zones.isEmpty()) {
|
||||
Logger.error("ABORT! Cache zone templates failed to load!");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
ZoneManager.loadZoneTemplates();
|
||||
|
||||
Logger.info("Loading Rune Power Requirements");
|
||||
PowerReq.loadRunePowers();
|
||||
|
||||
@@ -526,17 +555,97 @@ public class WorldServer {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
staticBuildingMode = StaticBuildingLoader.getMode();
|
||||
staticZoneRows = DbManager.StaticWorldQueries.COUNT_STATIC_ZONE_ROWS();
|
||||
Logger.info("Static buildings: MB_STATIC_BUILDINGS " + staticBuildingMode + ", " + staticZoneRows + " static zone rows in obj_zone");
|
||||
|
||||
// The 2.4 migration deletes the static rows: JSON needs it run, DB and PARITY need it not run yet.
|
||||
|
||||
if (staticZoneRows < 0 || (staticBuildingMode == mbEnums.StaticBuildingMode.JSON) != (staticZoneRows == 0)) {
|
||||
Logger.error("ABORT! MB_STATIC_BUILDINGS " + staticBuildingMode + " does not fit this DB: " + staticZoneRows
|
||||
+ " static zone rows. Use JSON after the World Boot Plan 2.4 migration, DB or PARITY before it.");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
if (StaticManifest.isRenumberPending(worldMapID)) {
|
||||
Logger.error("ABORT! maps/" + worldMapID + ".renumber.sql exists: apply it with World-Boot-Plan sql/static_renumber.sql, then delete it.");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading NPC guilds");
|
||||
|
||||
if (!GuildManager.loadStaticGuilds(worldMapID)) {
|
||||
Logger.error("ABORT! Guild seed failed to load!");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Loading all Zones");
|
||||
DbManager.ZoneQueries.GET_ALL_ZONES();
|
||||
|
||||
if (!ZoneManager.loadZones(staticBuildingMode == mbEnums.StaticBuildingMode.JSON)) {
|
||||
Logger.error("ABORT! Zones failed to load!");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
GuildManager.logMissingGuildKeys();
|
||||
ZoneManager.loadRuinZones();
|
||||
|
||||
if (staticBuildingMode == mbEnums.StaticBuildingMode.JSON) {
|
||||
Logger.info("Loading static Buildings from the zone JSON");
|
||||
staticBuildingEntries = StaticBuildingLoader.buildStaticBuildings(true);
|
||||
}
|
||||
|
||||
Logger.info("Loading all Buildings");
|
||||
BuildingManager._npcBuildingOwners = DbManager.BuildingQueries.GET_NPC_BUILDING_OWNERS();
|
||||
DbManager.BuildingQueries.GET_ALL_BUILDINGS();
|
||||
|
||||
// NPC cities need the static trees. Until the 2.5 migration deletes the DB NPC cities, they load from the
|
||||
// DB and the JSON cities only take their IDs for the parity check.
|
||||
|
||||
if (staticBuildingMode == mbEnums.StaticBuildingMode.JSON) {
|
||||
|
||||
npcCityRows = DbManager.StaticWorldQueries.COUNT_NPC_CITY_ROWS();
|
||||
|
||||
if (npcCityRows < 0) {
|
||||
Logger.error("ABORT! NPC city rows could not be counted.");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("Static cities: " + npcCityRows + " NPC city rows in obj_city; " + (npcCityRows == 0 ? "loading from the zone JSON" : "parity only"));
|
||||
staticCityEntries = StaticCityLoader.buildStaticCities(staticBuildingEntries, npcCityRows == 0);
|
||||
}
|
||||
|
||||
Logger.info("Loading all Cities");
|
||||
DbManager.CityQueries.GET_ALL_CITIES();
|
||||
Logger.info("Loading all NPCs");
|
||||
DbManager.NPCQueries.GET_ALL_NPCS();
|
||||
|
||||
// NPCs are cached now; Building.runAfterLoad falls back to the NPC cache.
|
||||
BuildingManager._npcBuildingOwners.clear();
|
||||
|
||||
Logger.info("Loading all Mobiles");
|
||||
DbManager.MobQueries.GET_ALL_MOBS();
|
||||
|
||||
if (staticBuildingMode == mbEnums.StaticBuildingMode.PARITY) {
|
||||
Logger.info("Comparing static buildings with the zone JSON");
|
||||
StaticBuildingParity.run(worldMapID);
|
||||
}
|
||||
|
||||
if (npcCityRows > 0) {
|
||||
Logger.info("Comparing NPC cities with the zone JSON");
|
||||
StaticCityParity.run(worldMapID, staticCityEntries);
|
||||
}
|
||||
|
||||
movedStaticIDs = StaticManifest.writeManifest(worldMapID);
|
||||
StaticManifest.logOrphanReferences();
|
||||
|
||||
// DB references still hold the old IDs; play would put them on the wrong objects.
|
||||
|
||||
if (movedStaticIDs > 0) {
|
||||
Logger.error("ABORT! " + movedStaticIDs + " static IDs moved. Apply maps/" + worldMapID
|
||||
+ ".renumber.sql with World-Boot-Plan sql/static_renumber.sql, delete it, then boot again.");
|
||||
exitServer(1);
|
||||
}
|
||||
|
||||
Logger.info("time to load World Objects: " + (System.currentTimeMillis() - start) + " ms");
|
||||
|
||||
// Configure realms for serialization
|
||||
|
||||
@@ -245,6 +245,16 @@ public class Hasher {
|
||||
return encode(numbers, alphabet_, salt_, minHashLength_);
|
||||
}
|
||||
|
||||
// encode() fails on negative numbers, and static world objects have negative IDs. A static ID hashes as
|
||||
// the pair (0, -id), which no single positive ID can produce.
|
||||
public String encryptObjectID(long objectID) {
|
||||
|
||||
if (objectID < 0)
|
||||
return encrypt(0, -objectID);
|
||||
|
||||
return encrypt(objectID);
|
||||
}
|
||||
|
||||
public long[] decrypt(String hash) {
|
||||
return decode(hash);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user