Compare commits

..
Author SHA1 Message Date
SlaveOfMagicbot 86062cf712 fix: null-safe upgradeDateTime conversion in BuildingManager.setUpgradeDateTime
Placing a banestone (and other callers) pass null to clear a fresh
building's upgrade timer, but the inline .atZone(...).toInstant() NPE'd
on null. Convert only when non-null; the DB layer already handles null.
2026-10-12 16:47:26 -05:00
SlaveOfMagicbot 85cd4e0c14 fix: use LocalDateTime for mine late-window calc in ArcMineWindowAvailableTimeMsg
Instant has no hour-of-day field, so .with(HOUR_OF_DAY, ...) threw
UnsupportedTemporalTypeException. Switch lateTime to LocalDateTime so the
late-window (midnight) time can be set and the seconds-remaining computed.
2026-10-12 12:44:27 -05:00
SlaveOfMagicbot 8a136d84f4 refactor: migrate JodaTime to java.time (rebuild on subdate2 ab96cfc)
Complete the JodaTime -> java.time migration across 30 files on the
correct subdate2 base. No org.joda references remain.
2026-10-09 20:12:23 -05:00
60 changed files with 403 additions and 2760 deletions

No files matched your search

@@ -468,12 +468,7 @@ public enum InterestManager implements Runnable {
continue; continue;
awonpc.playerAgroMap.put(player.getObjectUUID(), 0f); awonpc.playerAgroMap.put(player.getObjectUUID(), 0f);
((Mob) awonpc).setCombatTarget(null);
// siege engines keep firing at their target when a new player loads them
if (!awonpc.isSiege())
((Mob) awonpc).setCombatTarget(null);
lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii()); lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
} else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) { } else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) {
+10 -11
View File
@@ -14,7 +14,6 @@ import engine.mbEnums;
import engine.objects.Bane; import engine.objects.Bane;
import engine.objects.City; import engine.objects.City;
import engine.workthreads.WarehousePushThread; import engine.workthreads.WarehousePushThread;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.*; import java.sql.*;
@@ -34,8 +33,8 @@ public class BaneRecord extends DataRecord {
private String baneDropperHash; private String baneDropperHash;
private String baneGuildHash; private String baneGuildHash;
private String baneNationHash; private String baneNationHash;
private DateTime baneLiveTime; private LocalDateTime baneLiveTime;
private DateTime baneDropTime; private LocalDateTime baneDropTime;
private BaneRecord(Bane bane) { private BaneRecord(Bane bane) {
this.recordType = mbEnums.DataRecordType.BANE; this.recordType = mbEnums.DataRecordType.BANE;
@@ -129,9 +128,9 @@ public class BaneRecord extends DataRecord {
return outStatement; return outStatement;
} }
public static DateTime getLastBaneDateTime(City city) { public static LocalDateTime getLastBaneDateTime(City city) {
DateTime outDateTime = null; LocalDateTime outDateTime = null;
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement statement = buildDateTimeQueryStatement(connection, city); PreparedStatement statement = buildDateTimeQueryStatement(connection, city);
@@ -139,7 +138,7 @@ public class BaneRecord extends DataRecord {
while (rs.next()) { while (rs.next()) {
outDateTime = new DateTime(rs.getTimestamp("endDatetime")); outDateTime = rs.getTimestamp("endDatetime").toLocalDateTime();
} }
@@ -160,7 +159,7 @@ public class BaneRecord extends DataRecord {
} }
public static void updateLiveDate(Bane bane, DateTime dateTime) { public static void updateLiveDate(Bane bane, LocalDateTime dateTime) {
if (bane == null) if (bane == null)
return; return;
@@ -175,13 +174,13 @@ public class BaneRecord extends DataRecord {
} }
} }
private static PreparedStatement buildUpdateLiveDateStatement(Connection connection, Bane bane, DateTime dateTime) throws SQLException { private static PreparedStatement buildUpdateLiveDateStatement(Connection connection, Bane bane, LocalDateTime dateTime) throws SQLException {
PreparedStatement outStatement = null; PreparedStatement outStatement = null;
String queryString = "UPDATE `warehouse_banehistory` SET `liveDatetime` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'"; String queryString = "UPDATE `warehouse_banehistory` SET `liveDatetime` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'";
outStatement = connection.prepareStatement(queryString); outStatement = connection.prepareStatement(queryString);
outStatement.setTimestamp(1, new java.sql.Timestamp(dateTime.getMillis())); outStatement.setTimestamp(1, Timestamp.valueOf(dateTime));
outStatement.setString(2, bane.getCity().getHash()); outStatement.setString(2, bane.getCity().getHash());
return outStatement; return outStatement;
@@ -366,12 +365,12 @@ public class BaneRecord extends DataRecord {
if (this.baneDropTime == null) if (this.baneDropTime == null)
outStatement.setNull(8, java.sql.Types.DATE); outStatement.setNull(8, java.sql.Types.DATE);
else else
outStatement.setTimestamp(8, new java.sql.Timestamp(this.baneDropTime.getMillis())); outStatement.setTimestamp(8, Timestamp.valueOf(this.baneDropTime));
if (this.baneLiveTime == null) if (this.baneLiveTime == null)
outStatement.setNull(9, java.sql.Types.DATE); outStatement.setNull(9, java.sql.Types.DATE);
else else
outStatement.setTimestamp(9, new java.sql.Timestamp(this.baneLiveTime.getMillis())); outStatement.setTimestamp(9, Timestamp.valueOf(this.baneLiveTime));
outStatement.setString(10, this.eventType.name()); outStatement.setString(10, this.eventType.name());
+3 -3
View File
@@ -14,7 +14,7 @@ import engine.objects.Bane;
import engine.objects.Building; import engine.objects.Building;
import engine.objects.City; import engine.objects.City;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import org.joda.time.DateTime; import java.time.LocalDateTime;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection; import java.sql.Connection;
@@ -71,12 +71,12 @@ public class dbBaneHandler extends dbHandlerBase {
} }
public boolean SET_BANE_TIME(DateTime toSet, int cityUUID) { public boolean SET_BANE_TIME(LocalDateTime toSet, int cityUUID) {
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_banes` SET `liveDate`=? WHERE `cityUUID`=?")) { PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_banes` SET `liveDate`=? WHERE `cityUUID`=?")) {
preparedStatement.setTimestamp(1, new java.sql.Timestamp(toSet.getMillis())); preparedStatement.setTimestamp(1, java.sql.Timestamp.valueOf(toSet));
preparedStatement.setLong(2, cityUUID); preparedStatement.setLong(2, cityUUID);
preparedStatement.execute(); preparedStatement.execute();
@@ -17,7 +17,9 @@ import engine.mbEnums.DbObjectType;
import engine.mbEnums.ProtectionState; import engine.mbEnums.ProtectionState;
import engine.mbEnums.TaxType; import engine.mbEnums.TaxType;
import engine.objects.*; import engine.objects.*;
import org.joda.time.DateTime; import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection; import java.sql.Connection;
@@ -40,7 +42,7 @@ public class dbBuildingHandler extends dbHandlerBase {
public Building CREATE_BUILDING(int parentZoneID, int OwnerUUID, String name, int meshUUID, public Building CREATE_BUILDING(int parentZoneID, int OwnerUUID, String name, int meshUUID,
Vector3fImmutable location, float meshScale, int currentHP, Vector3fImmutable location, float meshScale, int currentHP,
ProtectionState protectionState, int currentGold, int rank, ProtectionState protectionState, int currentGold, int rank,
DateTime upgradeDate, int blueprintUUID, float w, float rotY) { Instant upgradeDate, int blueprintUUID, float w, float rotY) {
Building building = null; Building building = null;
@@ -61,7 +63,7 @@ public class dbBuildingHandler extends dbHandlerBase {
preparedStatement.setInt(12, rank); preparedStatement.setInt(12, rank);
if (upgradeDate != null) if (upgradeDate != null)
preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis())); preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.toEpochMilli()));
else else
preparedStatement.setNull(13, java.sql.Types.DATE); preparedStatement.setNull(13, java.sql.Types.DATE);
@@ -749,7 +751,7 @@ public class dbBuildingHandler extends dbHandlerBase {
return false; return false;
} }
public boolean updateBuildingUpgradeTime(LocalDateTime upgradeDateTime, Building toUpgrade, int costToUpgrade) { public boolean updateBuildingUpgradeTime(Instant upgradeDateTime, Building toUpgrade, int costToUpgrade) {
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET upgradeDate=?, currentGold=? " PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET upgradeDate=?, currentGold=? "
@@ -758,8 +760,7 @@ public class dbBuildingHandler extends dbHandlerBase {
if (upgradeDateTime == null) if (upgradeDateTime == null)
preparedStatement.setNull(1, java.sql.Types.DATE); preparedStatement.setNull(1, java.sql.Types.DATE);
else else
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.atZone(ZoneId.systemDefault()) preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.toEpochMilli()));
.toInstant().toEpochMilli()));
preparedStatement.setInt(2, toUpgrade.getStrongboxValue() - costToUpgrade); preparedStatement.setInt(2, toUpgrade.getStrongboxValue() - costToUpgrade);
preparedStatement.setInt(3, toUpgrade.getObjectUUID()); preparedStatement.setInt(3, toUpgrade.getObjectUUID());
@@ -838,7 +839,7 @@ public class dbBuildingHandler extends dbHandlerBase {
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxDate=? " PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxDate=? "
+ "WHERE UID = ?")) { + "WHERE UID = ?")) {
preparedStatement.setTimestamp(1, new java.sql.Timestamp(DateTime.now().plusDays(7).getMillis())); preparedStatement.setTimestamp(1, new java.sql.Timestamp(Instant.now().plus(7, java.time.temporal.ChronoUnit.DAYS).toEpochMilli()));
preparedStatement.setInt(2, building.getObjectUUID()); preparedStatement.setInt(2, building.getObjectUUID());
return (preparedStatement.executeUpdate() > 0); return (preparedStatement.executeUpdate() > 0);
+3 -3
View File
@@ -14,7 +14,7 @@ import engine.mbEnums;
import engine.mbEnums.GuildHistoryType; import engine.mbEnums.GuildHistoryType;
import engine.objects.*; import engine.objects.*;
import engine.server.world.WorldServer; import engine.server.world.WorldServer;
import org.joda.time.DateTime; import java.time.Instant;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.*; import java.sql.*;
@@ -125,7 +125,7 @@ public class dbGuildHandler extends dbHandlerBase {
return false; return false;
} }
public boolean ADD_TO_GUILDHISTORY(int target, PlayerCharacter playerCharacter, DateTime historyDate, GuildHistoryType historyType) { public boolean ADD_TO_GUILDHISTORY(int target, PlayerCharacter playerCharacter, Instant historyDate, GuildHistoryType historyType) {
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_character_guildhistory` (`GuildID`, `CharacterID`, `historyDate`, `historyType`) VALUES (?,?,?,?)")) { PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_character_guildhistory` (`GuildID`, `CharacterID`, `historyDate`, `historyType`) VALUES (?,?,?,?)")) {
@@ -136,7 +136,7 @@ public class dbGuildHandler extends dbHandlerBase {
if (historyDate == null) if (historyDate == null)
preparedStatement.setNull(3, java.sql.Types.DATE); preparedStatement.setNull(3, java.sql.Types.DATE);
else else
preparedStatement.setTimestamp(3, new Timestamp(historyDate.getMillis())); preparedStatement.setTimestamp(3, java.sql.Timestamp.from(historyDate));
preparedStatement.setString(4, historyType.name()); preparedStatement.setString(4, historyType.name());
+4 -3
View File
@@ -12,7 +12,8 @@ package engine.db.handlers;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.objects.Mob; import engine.objects.Mob;
import org.joda.time.DateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection; import java.sql.Connection;
@@ -82,7 +83,7 @@ public class dbMobHandler extends dbHandlerBase {
return mobileList; return mobileList;
} }
public boolean updateUpgradeTime(Mob mob, DateTime upgradeDateTime) { public boolean updateUpgradeTime(Mob mob, LocalDateTime upgradeDateTime) {
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_mob SET upgradeDate=? " PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_mob SET upgradeDate=? "
@@ -91,7 +92,7 @@ public class dbMobHandler extends dbHandlerBase {
if (upgradeDateTime == null) if (upgradeDateTime == null)
preparedStatement.setNull(1, java.sql.Types.DATE); preparedStatement.setNull(1, java.sql.Types.DATE);
else else
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.getMillis())); preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
preparedStatement.setInt(2, mob.getObjectUUID()); preparedStatement.setInt(2, mob.getObjectUUID());
+5 -4
View File
@@ -15,7 +15,8 @@ import engine.mbEnums;
import engine.mbEnums.ProfitType; import engine.mbEnums.ProfitType;
import engine.objects.NPC; import engine.objects.NPC;
import engine.objects.NPCProfits; import engine.objects.NPCProfits;
import org.joda.time.DateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection; import java.sql.Connection;
@@ -57,7 +58,7 @@ public class dbNPCHandler extends dbHandlerBase {
runeSets.put(runeSetID, runeList); runeSets.put(runeSetID, runeList);
} else { } else {
ArrayList<Integer> runeList = runeSets.get(runeSetID); ArrayList<Integer> runeList = runeSets.get(runeSetID);
runeList.add(runeBaseID); runeList.add(runeSetID);
runeSets.put(runeSetID, runeList); runeSets.put(runeSetID, runeList);
} }
} }
@@ -240,7 +241,7 @@ public class dbNPCHandler extends dbHandlerBase {
} }
} }
public boolean updateUpgradeTime(NPC npc, DateTime upgradeDateTime) { public boolean updateUpgradeTime(NPC npc, LocalDateTime upgradeDateTime) {
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_npc SET upgradeDate=? " PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_npc SET upgradeDate=? "
@@ -249,7 +250,7 @@ public class dbNPCHandler extends dbHandlerBase {
if (upgradeDateTime == null) if (upgradeDateTime == null)
preparedStatement.setNull(1, java.sql.Types.DATE); preparedStatement.setNull(1, java.sql.Types.DATE);
else else
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.getMillis())); preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
preparedStatement.setInt(2, npc.getObjectUUID()); preparedStatement.setInt(2, npc.getObjectUUID());
+3 -3
View File
@@ -16,7 +16,7 @@ import engine.mbEnums.ProtectionState;
import engine.objects.AbstractGameObject; import engine.objects.AbstractGameObject;
import engine.objects.Building; import engine.objects.Building;
import engine.objects.Shrine; import engine.objects.Shrine;
import org.joda.time.DateTime; import java.time.Instant;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection; import java.sql.Connection;
@@ -53,7 +53,7 @@ public class dbShrineHandler extends dbHandlerBase {
public ArrayList<AbstractGameObject> CREATE_SHRINE(int parentZoneID, int OwnerUUID, String name, int meshUUID, public ArrayList<AbstractGameObject> CREATE_SHRINE(int parentZoneID, int OwnerUUID, String name, int meshUUID,
Vector3fImmutable location, float meshScale, int currentHP, Vector3fImmutable location, float meshScale, int currentHP,
ProtectionState protectionState, int currentGold, int rank, ProtectionState protectionState, int currentGold, int rank,
DateTime upgradeDate, int blueprintUUID, float w, float rotY, String shrineType) { Instant upgradeDate, int blueprintUUID, float w, float rotY, String shrineType) {
ArrayList<AbstractGameObject> shrineList = new ArrayList<>(); ArrayList<AbstractGameObject> shrineList = new ArrayList<>();
@@ -74,7 +74,7 @@ public class dbShrineHandler extends dbHandlerBase {
preparedStatement.setInt(12, rank); preparedStatement.setInt(12, rank);
if (upgradeDate != null) if (upgradeDate != null)
preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis())); preparedStatement.setTimestamp(13, java.sql.Timestamp.from(upgradeDate));
else else
preparedStatement.setNull(13, java.sql.Types.DATE); preparedStatement.setNull(13, java.sql.Types.DATE);
+8 -22
View File
@@ -20,9 +20,8 @@ import engine.objects.City;
import engine.objects.Transaction; import engine.objects.Transaction;
import engine.objects.Warehouse; import engine.objects.Warehouse;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import java.time.Instant;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -41,7 +40,7 @@ public class dbWarehouseHandler extends dbHandlerBase {
} }
public boolean CREATE_TRANSACTION(int warehouseBuildingID, GameObjectType targetType, int targetUUID, TransactionType transactionType, mbEnums.ResourceType resource, int amount, DateTime date) { public boolean CREATE_TRANSACTION(int warehouseBuildingID, GameObjectType targetType, int targetUUID, TransactionType transactionType, mbEnums.ResourceType resource, int amount, Instant date) {
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_warehouse_transactions` (`warehouseUID`, `targetType`,`targetUID`, `type`,`resource`,`amount`,`date` ) VALUES (?,?,?,?,?,?,?)")) { PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_warehouse_transactions` (`warehouseUID`, `targetType`,`targetUID`, `type`,`resource`,`amount`,`date` ) VALUES (?,?,?,?,?,?,?)")) {
@@ -52,7 +51,7 @@ public class dbWarehouseHandler extends dbHandlerBase {
preparedStatement.setString(4, transactionType.name()); preparedStatement.setString(4, transactionType.name());
preparedStatement.setString(5, resource.name()); preparedStatement.setString(5, resource.name());
preparedStatement.setInt(6, amount); preparedStatement.setInt(6, amount);
preparedStatement.setTimestamp(7, new java.sql.Timestamp(date.getMillis())); preparedStatement.setTimestamp(7, new java.sql.Timestamp(date.toEpochMilli()));
return (preparedStatement.executeUpdate() > 0); return (preparedStatement.executeUpdate() > 0);
@@ -135,6 +134,7 @@ public class dbWarehouseHandler extends dbHandlerBase {
while (rs.next()) { while (rs.next()) {
int cityUID = rs.getInt("cityUUID"); int cityUID = rs.getInt("cityUUID");
JSONObject jsonObject = new JSONObject(rs.getString("warehouse"));
City city = City.getCity(cityUID); City city = City.getCity(cityUID);
if (city == null) { if (city == null) {
@@ -142,18 +142,12 @@ public class dbWarehouseHandler extends dbHandlerBase {
continue; continue;
} }
try { city.warehouse = new Warehouse(jsonObject);
city.warehouse = new Warehouse(new JSONObject(rs.getString("warehouse")));
} catch (JSONException e) {
Logger.error("Warehouse for city " + cityUID + " failed to load: " + e.getMessage());
continue;
}
city.warehouse.city = city; city.warehouse.city = city;
// Locate warehouse building // Locate warehouse building
for (Building building : city.parentZone.zoneBuildingSet) { for (Building building : city.parentZone.zoneBuildingSet) {
if (building.getBlueprint().getBuildingGroup().equals(mbEnums.BuildingGroup.WAREHOUSE) && building.getRank() != -1) { if (building.getBlueprint().getBuildingGroup().equals(mbEnums.BuildingGroup.WAREHOUSE)) {
city.warehouse.building = building; city.warehouse.building = building;
break; break;
} }
@@ -211,16 +205,8 @@ public class dbWarehouseHandler extends dbHandlerBase {
ResultSet rs = preparedStatement.executeQuery()) { ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) { while (rs.next()) {
int workOrderID = rs.getInt("workorderID"); JSONObject jsonObject = new JSONObject(rs.getString("workorder"));
WorkOrder workOrder; WorkOrder workOrder = new WorkOrder(jsonObject);
try {
workOrder = new WorkOrder(new JSONObject(rs.getString("workorder")));
} catch (JSONException e) {
Logger.error("WorkOrder " + workOrderID + " failed to load: " + e.getMessage());
continue;
}
submitList.add(workOrder); submitList.add(workOrder);
} }
} catch (Exception e) { } catch (Exception e) {
+3 -2
View File
@@ -62,10 +62,11 @@ public class MakeItemCmd extends AbstractDevCmd {
ItemTemplate template; ItemTemplate template;
try { try {
templateID = Integer.parseInt(words[0]); templateID = Integer.parseInt(words[0]);
template = ItemTemplate.templates.get(templateID); template = ItemTemplate.templates.get(words[0].toLowerCase());
if (template == null) { if (template == null) {
throwbackError(pc, "No item template with id " + words[0]); throwbackError(pc, "Supplied type " + words[0]
+ " failed to parse to an Integer");
return; return;
} }
+10 -11
View File
@@ -25,6 +25,7 @@ import engine.objects.*;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
@@ -211,19 +212,19 @@ public enum BuildingManager {
break; break;
case WAREHOUSE: case WAREHOUSE:
Warehouse warehouse = Warehouse.destroyedWarehouses.get(building.getObjectUUID()); City city = building.getCity();
if (city == null)
return true;
Warehouse warehouse = city.warehouse;
if (warehouse == null) if (warehouse == null)
return false; return false;
for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) { for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) {
ItemTemplate resourceTemplate = ItemTemplate.templates.get(resourceType.templateID); if (!player.charItemManager.hasRoomInventory(resourceType.template.item_wt)) {
if (resourceTemplate == null)
continue;
if (!player.charItemManager.hasRoomInventory(resourceTemplate.item_wt)) {
ChatManager.chatSystemInfo(player, "You can not carry any more of that item."); ChatManager.chatSystemInfo(player, "You can not carry any more of that item.");
return false; return false;
} }
@@ -241,9 +242,6 @@ public enum BuildingManager {
} }
} }
if (Warehouse.isEmpty(warehouse))
Warehouse.destroyedWarehouses.remove(building.getObjectUUID());
break; break;
} }
@@ -277,7 +275,8 @@ public enum BuildingManager {
if (building == null) if (building == null)
return; return;
if (!DbManager.BuildingQueries.updateBuildingUpgradeTime(upgradeDateTime, building, rankCost)) { Instant instant = (upgradeDateTime == null) ? null : upgradeDateTime.atZone(ZoneId.systemDefault()).toInstant();
if (!DbManager.BuildingQueries.updateBuildingUpgradeTime(instant, building, rankCost)) {
Logger.error("Failed to set upgradeTime for building " + building.getObjectUUID()); Logger.error("Failed to set upgradeTime for building " + building.getObjectUUID());
return; return;
} }
+5 -110
View File
@@ -14,7 +14,6 @@ import engine.jobs.AttackJob;
import engine.jobs.DeferredPowerJob; import engine.jobs.DeferredPowerJob;
import engine.mbEnums; import engine.mbEnums;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.PowerProjectileMsg;
import engine.net.client.msg.TargetedActionMsg; import engine.net.client.msg.TargetedActionMsg;
import engine.net.client.msg.UpdateStateMsg; import engine.net.client.msg.UpdateStateMsg;
import engine.objects.*; import engine.objects.*;
@@ -147,19 +146,11 @@ public enum CombatManager {
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None); rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
attackRange += weapon.template.item_weapon_max_range * rangeMod; attackRange += weapon.template.item_weapon_max_range * rangeMod;
} else if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) {
//unarmed mob, add the natural attack range from its rune (0 for most melee mobs)
if (bonus != null)
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
attackRange += getNaturalAttackRange(attacker, slot) * rangeMod;
} }
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
if (((Mob) attacker).isSiege()) if (((Mob) attacker).isSiege())
attackRange = getSiegeRange((Mob) attacker); attackRange = 300;
float distanceSquared = attacker.loc.distanceSquared(target.loc); float distanceSquared = attacker.loc.distanceSquared(target.loc);
@@ -223,22 +214,6 @@ public enum CombatManager {
wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects. wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects.
delay = (long)wepSpeed * 100L; delay = (long)wepSpeed * 100L;
} else {
//unarmed, use the natural attack speed from the mob or race rune
float naturalSpeed = getNaturalAttackSpeed(attacker, slot);
if (naturalSpeed > 0) {
if (attacker.getBonuses() != null && attacker.getBonuses().getFloatPercentAll(mbEnums.ModType.AttackDelay, mbEnums.SourceType.None) != 0f) //add effects speed bonus
naturalSpeed *= (1 + attacker.getBonuses().getFloatPercentAll(mbEnums.ModType.AttackDelay, mbEnums.SourceType.None));
if (naturalSpeed < 10)
naturalSpeed = 10;
delay = (long) naturalSpeed * 100L;
}
} }
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
@@ -257,8 +232,7 @@ public enum CombatManager {
//handle retaliate //handle retaliate
//siege engines only retaliate on damage, see AbstractCharacter.modifyHealth if (AbstractCharacter.IsAbstractCharacter(target)) {
if (AbstractCharacter.IsAbstractCharacter(target) && !(target.getObjectType().equals(mbEnums.GameObjectType.Mob) && ((Mob) target).isSiege())) {
if (((AbstractCharacter) target).combatTarget == null || !((AbstractCharacter) target).combatTarget.isAlive()) { if (((AbstractCharacter) target).combatTarget == null || !((AbstractCharacter) target).combatTarget.isAlive()) {
((AbstractCharacter) target).combatTarget = attacker; ((AbstractCharacter) target).combatTarget = attacker;
if (target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && ((AbstractCharacter) target).isCombat()) if (target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && ((AbstractCharacter) target).isCombat())
@@ -345,7 +319,7 @@ public enum CombatManager {
DispatchManager.sendToAllInRange(attacker, msg); DispatchManager.sendToAllInRange(attacker, msg);
//we need to send the animation even if the attacker misses //we need to send the animation even if the attacker misses
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot)); TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
DispatchManager.sendToAllInRange(target, cmm); DispatchManager.sendToAllInRange(target, cmm);
//set auto attack job //set auto attack job
@@ -385,7 +359,7 @@ public enum CombatManager {
DispatchManager.dispatchMsgToInterestArea(target, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); DispatchManager.dispatchMsgToInterestArea(target, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
//we need to send the animation even if the attacker misses //we need to send the animation even if the attacker misses
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot)); TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
DispatchManager.sendToAllInRange(target, cmm); DispatchManager.sendToAllInRange(target, cmm);
//set auto attack job //set auto attack job
setAutoAttackJob(attacker, slot, delay); setAutoAttackJob(attacker, slot, delay);
@@ -473,7 +447,7 @@ public enum CombatManager {
if (resists.immuneTo(damageType)) { if (resists.immuneTo(damageType)) {
//set auto attack job //set auto attack job
//we need to send the animation even if the attacker misses //we need to send the animation even if the attacker misses
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot)); TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
DispatchManager.sendToAllInRange(target, cmm); DispatchManager.sendToAllInRange(target, cmm);
setAutoAttackJob(attacker, slot, delay); setAutoAttackJob(attacker, slot, delay);
return; return;
@@ -505,15 +479,6 @@ public enum CombatManager {
} }
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) damage, attackAnim); TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) damage, attackAnim);
DispatchManager.sendToAllInRange(target, cmm); DispatchManager.sendToAllInRange(target, cmm);
//siege projectile is sent once per hit, not per AI pulse
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob) && ((Mob) attacker).isSiege()
&& target.getObjectType().equals(mbEnums.GameObjectType.Building)) {
PowerProjectileMsg ppm = new PowerProjectileMsg(attacker, target);
ppm.setRange(50);
DispatchManager.dispatchMsgToInterestArea(attacker, ppm, mbEnums.DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
}
} }
} }
@@ -671,76 +636,6 @@ public enum CombatManager {
} }
public static float getNaturalAttackSpeed(AbstractCharacter attacker, mbEnums.EquipSlotType slot) {
// Returns the rune natural attack speed (x100 ms) for this hand, or 0 if the rune has none.
float[] attack = getNaturalAttack(attacker, slot);
if (attack == null || attack.length < 1)
return 0;
return attack[0];
}
public static float getNaturalAttackRange(AbstractCharacter attacker, mbEnums.EquipSlotType slot) {
// Returns the rune natural attack range for this hand, or 0 if the rune has none.
float[] attack = getNaturalAttack(attacker, slot);
if (attack == null || attack.length < 2)
return 0;
return attack[1];
}
public static float getSiegeRange(Mob siegeEngine) {
// Siege engines use the range from their rune natural attack (350 for all three), 300 if the rune has none.
float range = getNaturalAttackRange(siegeEngine, mbEnums.EquipSlotType.RHELD);
if (range <= 0)
return 300;
return range;
}
public static float[] getNaturalAttack(AbstractCharacter attacker, mbEnums.EquipSlotType slot) {
// Returns the rune natural attack entry for this hand, or null if the rune has none.
// Mobs use their mobbase rune, players use their race rune.
int runeID;
ItemTemplate rune;
int hand = slot.equals(mbEnums.EquipSlotType.LHELD) ? 2 : 1;
switch (attacker.getObjectType()) {
case Mob:
runeID = ((Mob) attacker).loadID;
break;
case PlayerCharacter:
runeID = ((PlayerCharacter) attacker).getRaceID();
break;
default:
return null;
}
rune = ItemTemplate.templates.get(runeID);
if (rune == null || rune.rune_natural_attacks.isEmpty())
return null;
for (float[] attack : rune.rune_natural_attacks)
if (attack.length > 3 && (int) attack[3] == hand)
return attack;
// No entry for this hand, fall back to the first natural attack
return rune.rune_natural_attacks.get(0);
}
public static int calculatePetDamage(AbstractCharacter agent) { public static int calculatePetDamage(AbstractCharacter agent) {
//damage calc for pet //damage calc for pet
float range; float range;
@@ -75,7 +75,6 @@ public enum ConfigManager {
// Mobile AI modifiers // Mobile AI modifiers
MB_AI_CAST_FREQUENCY, MB_AI_CAST_FREQUENCY,
MB_AI_AGGRO_RANGE, MB_AI_AGGRO_RANGE,
MB_MINION_SPAWN_TIME,
//drop rates //drop rates
MB_NORMAL_EXP_RATE, MB_NORMAL_EXP_RATE,
+5 -5
View File
@@ -16,7 +16,7 @@ import engine.net.client.ClientConnection;
import engine.net.client.msg.guild.AcceptInviteToGuildMsg; import engine.net.client.msg.guild.AcceptInviteToGuildMsg;
import engine.net.client.msg.guild.GuildInfoMsg; import engine.net.client.msg.guild.GuildInfoMsg;
import engine.objects.*; import engine.objects.*;
import org.joda.time.DateTime; import java.time.Instant;
public enum GuildManager { public enum GuildManager {
@@ -67,8 +67,8 @@ public enum GuildManager {
return false; return false;
if (playerCharacter.getGuild() != null && !playerCharacter.getGuild().isEmptyGuild()) { if (playerCharacter.getGuild() != null && !playerCharacter.getGuild().isEmptyGuild()) {
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(playerCharacter.getGuildUUID(), playerCharacter, DateTime.now(), GuildHistoryType.LEAVE)) { if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(playerCharacter.getGuildUUID(), playerCharacter, Instant.now(), GuildHistoryType.LEAVE)) {
GuildHistory guildHistory = new GuildHistory(playerCharacter.getGuildUUID(), playerCharacter.getGuild().getName(), DateTime.now(), GuildHistoryType.LEAVE); GuildHistory guildHistory = new GuildHistory(playerCharacter.getGuildUUID(), playerCharacter.getGuild().getName(), Instant.now(), GuildHistoryType.LEAVE);
playerCharacter.getGuildHistory().add(guildHistory); playerCharacter.getGuildHistory().add(guildHistory);
} }
} }
@@ -90,8 +90,8 @@ public enum GuildManager {
dispatch = Dispatch.borrow(playerCharacter, msg); dispatch = Dispatch.borrow(playerCharacter, msg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY); DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
} }
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), playerCharacter, DateTime.now(), historyType)) { if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), playerCharacter, Instant.now(), historyType)) {
GuildHistory guildHistory = new GuildHistory(guild.getObjectUUID(), guild.getName(), DateTime.now(), historyType); GuildHistory guildHistory = new GuildHistory(guild.getObjectUUID(), guild.getName(), Instant.now(), historyType);
playerCharacter.getGuildHistory().add(guildHistory); playerCharacter.getGuildHistory().add(guildHistory);
} }
-5
View File
@@ -480,11 +480,6 @@ public enum NPCManager {
npc.runes.add(shopkeeper); npc.runes.add(shopkeeper);
if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) { if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) {
for (int runeID : _runeSetMap.get(npc.runeSetID)) { for (int runeID : _runeSetMap.get(npc.runeSetID)) {
// Shopkeeper is already applied above and most rune sets repeat it
if (runeID == 252620)
continue;
RuneBase rb = RuneBase.getRuneBase(runeID); RuneBase rb = RuneBase.getRuneBase(runeID);
if (rb != null) { if (rb != null) {
CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID()); CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID());
+3 -6
View File
@@ -11,7 +11,7 @@ package engine.jobs;
import engine.job.AbstractScheduleJob; import engine.job.AbstractScheduleJob;
import engine.objects.Bane; import engine.objects.Bane;
import org.joda.time.DateTime; import java.time.LocalDateTime;
public class BaneDefaultTimeJob extends AbstractScheduleJob { public class BaneDefaultTimeJob extends AbstractScheduleJob {
@@ -31,11 +31,8 @@ public class BaneDefaultTimeJob extends AbstractScheduleJob {
return; return;
} }
DateTime defaultTime = new DateTime(this.bane.getPlacementDate()); LocalDateTime defaultTime = this.bane.getPlacementDate()
defaultTime = defaultTime.plusDays(2); .plusDays(2).withHour(22).withMinute(0).withSecond(0).withNano(0);
defaultTime = defaultTime.hourOfDay().setCopy(22);
defaultTime = defaultTime.minuteOfHour().setCopy(0);
defaultTime = defaultTime.secondOfMinute().setCopy(0);
this.bane.setLiveDate(defaultTime); this.bane.setLiveDate(defaultTime);
} }
+30
View File
@@ -583,6 +583,36 @@ final public class FastMath {
return ret.normalize(); return ret.normalize();
} }
/**
* Turns a String into an Integer hash that the client will recognize.
*
* @param s The String to hash
* @return the Integer hash
*/
public static int hash(String s) {
int out = 0, pad = 0;
for (char c : s.toCharArray()) {
if (c == ' ' || c == '\\') {
pad += 5;
continue;
}
if (c > 0x40 && c < 0x60)
out ^= _rotl((byte) 0x60, pad);
out ^= _rotl((byte) (c & 0xDF), pad);
pad += 5;
}
return out;
}
/**
* Utility function for making hashes
*/
private static int _rotl(int value, int shift) {
if ((shift &= 31) == 0)
return value;
return (value << shift) | (value >> (32 - shift));
}
/** /**
* Gets number of seconds until next hours * Gets number of seconds until next hours
*/ */
+23 -30
View File
@@ -15,6 +15,7 @@ import engine.math.Vector2f;
import engine.math.Vector3f; import engine.math.Vector3f;
import engine.math.Vector3fImmutable; import engine.math.Vector3fImmutable;
import engine.objects.AbstractCharacter; import engine.objects.AbstractCharacter;
import engine.objects.ItemTemplate;
import engine.objects.Shrine; import engine.objects.Shrine;
import engine.objects.Zone; import engine.objects.Zone;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
@@ -823,7 +824,6 @@ public class mbEnums {
ACID, ACID,
DISEASE, DISEASE,
UNKNOWN, UNKNOWN,
ANTISIEGE,
// these added for immunities // these added for immunities
ATTACK, ATTACK,
POWERS, POWERS,
@@ -842,26 +842,34 @@ public class mbEnums {
public static DamageType getDamageType(String modName) { public static DamageType getDamageType(String modName) {
String name;
if (modName.isEmpty()) if (modName.isEmpty())
return DamageType.NONE; return DamageType.NONE;
// Client and DB strings vary in case and punctuation ("Fire", "Anti-Siege") if(modName.toLowerCase().equals("blind"))
modName = "BLINDNESS";
name = modName.replaceAll("[\\s,-]", "").toUpperCase(); if(modName.toLowerCase().equals("powerblock"))
modName = "POWERINHIBITOR";
if (name.equals("BLIND")) //validity check for not looking up damage type that doesn't exist
name = "BLINDNESS"; boolean valid = false;
for(DamageType type : DamageType.values()){
if(type.name().equals(modName))
valid = true;
}
if (name.equals("POWERBLOCK")) if(!valid)
name = "POWERINHIBITOR"; return DamageType.NONE;
for (DamageType type : DamageType.values()) DamageType damageType;
if (type.name().equals(name))
return type;
return DamageType.NONE; try {
damageType = DamageType.valueOf(modName.replace(",", "").toUpperCase());
} catch (Exception e) {
Logger.error(e);
return DamageType.NONE;
}
return damageType;
} }
} }
@@ -1501,15 +1509,6 @@ public class mbEnums {
} }
} }
// rune_type values from the client cache data (COBJECTS/RUNE)
public enum RuneType {
CLASS,
DISCIPLINE,
RACE,
TALENT
}
public enum ShrineType { public enum ShrineType {
Aelfborn(1701900, -75506007, true), Aelfborn(1701900, -75506007, true),
@@ -2762,6 +2761,7 @@ public class mbEnums {
public static HashMap<Integer, ResourceType> templateHashLookup = new HashMap<>(); public static HashMap<Integer, ResourceType> templateHashLookup = new HashMap<>();
public static HashMap<Integer, ResourceType> resourceHashLookup = new HashMap<>(); public static HashMap<Integer, ResourceType> resourceHashLookup = new HashMap<>();
public int templateID; public int templateID;
public ItemTemplate template;
public int resourceHash; public int resourceHash;
public int templateHash; public int templateHash;
public int deposit_limit; public int deposit_limit;
@@ -2769,6 +2769,7 @@ public class mbEnums {
ResourceType(int templateID, int resourceHash, int templateHash, int deposit_limit, int mine_production) { ResourceType(int templateID, int resourceHash, int templateHash, int deposit_limit, int mine_production) {
this.templateID = templateID; this.templateID = templateID;
this.template = ItemTemplate.templates.get(this.templateID);
this.resourceHash = resourceHash; this.resourceHash = resourceHash;
this.templateHash = templateHash; this.templateHash = templateHash;
this.deposit_limit = deposit_limit; this.deposit_limit = deposit_limit;
@@ -2907,14 +2908,6 @@ public class mbEnums {
EMPTY_NEWITEM; EMPTY_NEWITEM;
} }
// Cache JSON names. UNKNOWN is raw 0, which the client treats as no slot (sb.exe 0x4AC810).
public enum SheathSlot {
UNKNOWN,
BACK,
WAIST,
NONE
}
public enum AttributeType { public enum AttributeType {
None, None,
Strength, Strength,
+56 -50
View File
@@ -14,10 +14,12 @@ import engine.gameManager.*;
import engine.math.Vector3f; import engine.math.Vector3f;
import engine.math.Vector3fImmutable; import engine.math.Vector3fImmutable;
import engine.mbEnums; import engine.mbEnums;
import engine.mbEnums.DispatchChannel;
import engine.mobileAI.Threads.MobAIThread; import engine.mobileAI.Threads.MobAIThread;
import engine.mobileAI.Threads.ReSpawner; import engine.mobileAI.Threads.ReSpawner;
import engine.mobileAI.utilities.MovementUtilities; import engine.mobileAI.utilities.MovementUtilities;
import engine.net.client.msg.PerformActionMsg; import engine.net.client.msg.PerformActionMsg;
import engine.net.client.msg.PowerProjectileMsg;
import engine.objects.*; import engine.objects.*;
import engine.powers.ActionsBase; import engine.powers.ActionsBase;
import engine.powers.PowersBase; import engine.powers.PowersBase;
@@ -44,11 +46,6 @@ public class MobAI {
// //
// CombatManager.class implements shared combat routines for all avatars. // CombatManager.class implements shared combat routines for all avatars.
// Pet aggro scan in checkForAggro stays off until it checks rune enemy lists,
// filters its targets and keeps pet targets (MBDATA/docs/plan-mask-pet.md steps 2-4).
private static final boolean PET_AGGRO_ENABLED = false;
private static void attackTarget(Mob mob, AbstractWorldObject target) { private static void attackTarget(Mob mob, AbstractWorldObject target) {
try { try {
@@ -96,7 +93,7 @@ public class MobAI {
mob.updateLocation(); mob.updateLocation();
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage());
} }
} }
@@ -115,9 +112,7 @@ public class MobAI {
if (mob.behaviourType.callsForHelp) if (mob.behaviourType.callsForHelp)
mobCallForHelp(mob); mobCallForHelp(mob);
// siege engines drop targets on their own attack range in determineAction if (!MovementUtilities.inRangeDropAggro(mob, target)) {
if (!mob.isSiege() && !MovementUtilities.inRangeDropAggro(mob, target)) {
mob.setCombatTarget(null); mob.setCombatTarget(null);
return; return;
} }
@@ -138,7 +133,7 @@ public class MobAI {
target.getPet().setCombatTarget(mob); target.getPet().setCombatTarget(mob);
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackPlayer"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackPlayer" + " " + e.getMessage());
} }
} }
@@ -160,10 +155,20 @@ public class MobAI {
if (guard.getCombatTarget() == null && !guard.getGuild().equals(mob.getGuild())) if (guard.getCombatTarget() == null && !guard.getGuild().equals(mob.getGuild()))
guard.setCombatTarget(mob); guard.setCombatTarget(mob);
if (mob.isSiege())
MovementManager.sendRWSSMsg(mob);
CombatManager.combatCycle(mob, target); CombatManager.combatCycle(mob, target);
if (mob.isSiege()) {
PowerProjectileMsg ppm = new PowerProjectileMsg(mob, target);
ppm.setRange(50);
DispatchManager.dispatchMsgToInterestArea(mob, ppm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
}
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackBuilding"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackBuilding" + " " + e.getMessage());
} }
} }
@@ -181,7 +186,7 @@ public class MobAI {
CombatManager.combatCycle(mob, target); CombatManager.combatCycle(mob, target);
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackMob"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackMob" + " " + e.getMessage());
} }
} }
@@ -243,7 +248,7 @@ public class MobAI {
MovementUtilities.aiMove(mob, mob.destination, true); MovementUtilities.aiMove(mob, mob.destination, true);
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: Patrol"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage());
} }
} }
@@ -285,7 +290,7 @@ public class MobAI {
return mob.nextCastTime <= System.currentTimeMillis(); return mob.nextCastTime <= System.currentTimeMillis();
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: canCast"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: canCast" + " " + e.getMessage());
} }
return false; return false;
} }
@@ -382,7 +387,7 @@ public class MobAI {
return true; return true;
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCast"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCast" + " " + e.getMessage());
} }
return false; return false;
} }
@@ -447,7 +452,7 @@ public class MobAI {
mob.nextCallForHelp = System.currentTimeMillis() + 60000; mob.nextCallForHelp = System.currentTimeMillis() + 60000;
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCallForHelp"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCallForHelp" + " " + e.getMessage());
} }
} }
@@ -513,16 +518,7 @@ public class MobAI {
boolean bypassLoadedPlayerCheck = false; boolean bypassLoadedPlayerCheck = false;
if (mob.isPlayerGuard() || mob.isSiege()) { if (mob.isPlayerGuard() || mob.isSiege()) {
bypassLoadedPlayerCheck = true; bypassLoadedPlayerCheck = true;
if (mob.combatTarget != null && mob.combatTarget.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
if (mob.isSiege()) {
// siege engines hold a character target until it dies or leaves their attack range
if (mob.combatTarget != null && AbstractCharacter.IsAbstractCharacter(mob.combatTarget))
if (mob.combatTarget.loc.distanceSquared(mob.loc) > mob.getRange() * mob.getRange())
mob.setCombatTarget(null);
} else if (mob.combatTarget != null && mob.combatTarget.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
if (mob.combatTarget.loc.distanceSquared(mob.loc) > 10000) if (mob.combatTarget.loc.distanceSquared(mob.loc) > 10000)
mob.setCombatTarget(null); mob.setCombatTarget(null);
} }
@@ -548,7 +544,7 @@ public class MobAI {
PlayerCharacter target = (PlayerCharacter) mob.getCombatTarget(); PlayerCharacter target = (PlayerCharacter) mob.getCombatTarget();
if (!mob.isSiege() && !mob.playerAgroMap.containsKey(target.getObjectUUID())) { if (!mob.playerAgroMap.containsKey(target.getObjectUUID())) {
mob.setCombatTarget(null); mob.setCombatTarget(null);
return; return;
} }
@@ -579,7 +575,7 @@ public class MobAI {
break; break;
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: DetermineAction"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: DetermineAction" + " " + e.getMessage());
} }
} }
@@ -634,7 +630,7 @@ public class MobAI {
} }
} }
if (PET_AGGRO_ENABLED && aiAgent.getCombatTarget() == null) { if (aiAgent.getCombatTarget() == null) {
//look for pets to aggro if no players found to aggro //look for pets to aggro if no players found to aggro
@@ -653,7 +649,7 @@ public class MobAI {
} }
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForAggro"); Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForAggro" + " " + e.getMessage());
} }
} }
@@ -706,7 +702,7 @@ public class MobAI {
} }
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckMobMovement"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckMobMovement" + " " + e.getMessage());
} }
} }
@@ -750,7 +746,7 @@ public class MobAI {
ReSpawner.respawnQueue.put(aiAgent); ReSpawner.respawnQueue.put(aiAgent);
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn"); Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn" + " " + e.getMessage());
} }
} }
@@ -765,7 +761,7 @@ public class MobAI {
if (mob.getCombatTarget() == null) if (mob.getCombatTarget() == null)
return; return;
if (mob.getCombatTarget().getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && !MovementUtilities.inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) && !mob.agentType.equals(mbEnums.AIAgentType.PET) && !mob.isSiege()) { if (mob.getCombatTarget().getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && !MovementUtilities.inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) && !mob.agentType.equals(mbEnums.AIAgentType.PET)) {
mob.setCombatTarget(null); mob.setCombatTarget(null);
return; return;
@@ -773,7 +769,7 @@ public class MobAI {
attackTarget(mob, mob.getCombatTarget()); attackTarget(mob, mob.getCombatTarget());
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForAttack"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForAttack" + " " + e.getMessage());
} }
} }
@@ -816,7 +812,7 @@ public class MobAI {
mob.playerAgroMap.replaceAll((e, v) -> 0f); mob.playerAgroMap.replaceAll((e, v) -> 0f);
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckToSendMobHome"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckToSendMobHome" + " " + e.getMessage());
} }
} }
@@ -855,7 +851,7 @@ public class MobAI {
mob.updateMovementState(); mob.updateMovementState();
mob.updateLocation(); mob.updateLocation();
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: chaseTarget"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: chaseTarget" + " " + e.getMessage());
} }
} }
@@ -881,17 +877,13 @@ public class MobAI {
if (aggroMob.agentType.equals(mbEnums.AIAgentType.PET)) if (aggroMob.agentType.equals(mbEnums.AIAgentType.PET))
continue; continue;
//don't attack siege engines
if (aggroMob.isSiege())
continue;
if (mob.getLoc().distanceSquared2D(aggroMob.getLoc()) > sqr(50)) if (mob.getLoc().distanceSquared2D(aggroMob.getLoc()) > sqr(50))
continue; continue;
mob.setCombatTarget(aggroMob); mob.setCombatTarget(aggroMob);
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: SafeGuardAggro"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: SafeGuardAggro" + " " + e.getMessage());
} }
} }
@@ -921,7 +913,7 @@ public class MobAI {
checkForAttack(mob); //only check to attack if combat target is not null checkForAttack(mob); //only check to attack if combat target is not null
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardLogic"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardLogic" + " " + e.getMessage());
} }
} }
@@ -954,7 +946,7 @@ public class MobAI {
mob.setHealth(mob.getHealthMax()); mob.setHealth(mob.getHealthMax());
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: PetLogic"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: PetLogic" + " " + e.getMessage());
} }
} }
@@ -978,7 +970,7 @@ public class MobAI {
} }
checkForAttack(mob); checkForAttack(mob);
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: HamletGuardLogic"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: HamletGuardLogic" + " " + e.getMessage());
} }
} }
@@ -1051,7 +1043,7 @@ public class MobAI {
checkForAttack(mob); checkForAttack(mob);
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: DefaultLogic"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: DefaultLogic" + " " + e.getMessage());
} }
} }
@@ -1107,11 +1099,25 @@ public class MobAI {
return; return;
} }
} }
if (mob.getCombatTarget() == null) {
// guards never auto-aggro siege engines; they only respond when one damages a city building //look for siege equipment to aggro if no players found to aggro
HashSet<AbstractWorldObject> awoList = WorldGrid.getObjectsInRangePartial(mob, MobAIThread.AI_BASE_AGGRO_RANGE, MBServerStatics.MASK_SIEGE);
for (AbstractWorldObject awoMob : awoList) {
Mob aggroMob = (Mob) awoMob;
if (guardCanAggro(mob, aggroMob)) {
mob.setCombatTarget(aggroMob);
return;
}
}
}
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForPlayerGuardAggro"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForPlayerGuardAggro" + e.getMessage());
} }
} }
@@ -1174,7 +1180,7 @@ public class MobAI {
} }
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro" + " " + e.getMessage());
} }
return false; return false;
} }
@@ -1226,7 +1232,7 @@ public class MobAI {
} }
} }
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: randomGuardPatrolPoints"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: randomGuardPatrolPoints" + " " + e);
} }
} }
@@ -1261,7 +1267,7 @@ public class MobAI {
} }
return mostHatedTarget; return mostHatedTarget;
} catch (Exception e) { } catch (Exception e) {
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: ChangeTargetFromMostHated"); Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: ChangeTargetFromMostHated" + " " + e.getMessage());
} }
return null; return null;
} }
+9 -8
View File
@@ -12,7 +12,8 @@ package engine.net;
import engine.math.Vector3f; import engine.math.Vector3f;
import engine.math.Vector3fImmutable; import engine.math.Vector3fImmutable;
import engine.util.ByteBufferUtils; import engine.util.ByteBufferUtils;
import org.joda.time.DateTime; import java.time.Instant;
import java.time.ZoneId;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -57,13 +58,13 @@ public class ByteBufferWriter {
this.bb.putLong(x); this.bb.putLong(x);
} }
public synchronized void putDateTime(DateTime dateTime) { public synchronized void putDateTime(Instant dateTime) {
this.put((byte) dateTime.getDayOfMonth()); this.put((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalDate().getDayOfMonth());
this.put((byte) ((byte) dateTime.getMonthOfYear() - 1)); this.put((byte) ((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalDate().getMonthValue() - 1));
this.putInt(dateTime.getYear() - 1900); this.putInt(dateTime.atZone(ZoneId.systemDefault()).toLocalDate().getYear() - 1900);
this.put((byte) dateTime.getHourOfDay()); this.put((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalTime().getHour());
this.put((byte) dateTime.getMinuteOfHour()); this.put((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalTime().getMinute());
this.put((byte) dateTime.getSecondOfDay()); this.put((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalTime().getSecond());
} }
public synchronized void putLocalDateTime(LocalDateTime dateTime) { public synchronized void putLocalDateTime(LocalDateTime dateTime) {
+2 -2
View File
@@ -14,7 +14,7 @@ import engine.gameManager.ChatManager;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import java.time.Instant;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@@ -85,7 +85,7 @@ public class NetMsgFactory {
if (protocolMsg == Protocol.NONE) { if (protocolMsg == Protocol.NONE) {
String errorString = DateTime.now().toString() + origin.lastProtocol.name(); String errorString = Instant.now().toString() + origin.lastProtocol.name();
int errorCode = errorString.hashCode(); int errorCode = errorString.hashCode();
@@ -22,7 +22,7 @@ import engine.net.client.msg.guild.GuildInfoMsg;
import engine.objects.Guild; import engine.objects.Guild;
import engine.objects.GuildHistory; import engine.objects.GuildHistory;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import org.joda.time.DateTime; import java.time.Instant;
public class AcceptInviteToGuildHandler extends AbstractClientMsgHandler { public class AcceptInviteToGuildHandler extends AbstractClientMsgHandler {
@@ -90,8 +90,8 @@ public class AcceptInviteToGuildHandler extends AbstractClientMsgHandler {
//Add to guild History //Add to guild History
if (player.getGuild() != null) { if (player.getGuild() != null) {
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(player.getGuildUUID(), player, DateTime.now(), GuildHistoryType.JOIN)) { if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(player.getGuildUUID(), player, Instant.now(), GuildHistoryType.JOIN)) {
GuildHistory guildHistory = new GuildHistory(player.getGuildUUID(), player.getGuild().getName(), DateTime.now(), GuildHistoryType.JOIN); GuildHistory guildHistory = new GuildHistory(player.getGuildUUID(), player.getGuild().getName(), Instant.now(), GuildHistoryType.JOIN);
player.getGuildHistory().add(guildHistory); player.getGuildHistory().add(guildHistory);
} }
} }
@@ -25,7 +25,7 @@ import engine.objects.Guild;
import engine.objects.GuildHistory; import engine.objects.GuildHistory;
import engine.objects.GuildStatusController; import engine.objects.GuildStatusController;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import org.joda.time.DateTime; import java.time.Instant;
public class BanishUnbanishHandler extends AbstractClientMsgHandler { public class BanishUnbanishHandler extends AbstractClientMsgHandler {
@@ -95,8 +95,8 @@ public class BanishUnbanishHandler extends AbstractClientMsgHandler {
//Set guild history //Set guild history
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), toBanish, DateTime.now(), GuildHistoryType.BANISHED)) { if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), toBanish, Instant.now(), GuildHistoryType.BANISHED)) {
GuildHistory guildHistory = new GuildHistory(toBanish.getGuildUUID(), toBanish.getGuild().getName(), DateTime.now(), GuildHistoryType.BANISHED); GuildHistory guildHistory = new GuildHistory(toBanish.getGuildUUID(), toBanish.getGuild().getName(), Instant.now(), GuildHistoryType.BANISHED);
toBanish.getGuildHistory().add(guildHistory); toBanish.getGuildHistory().add(guildHistory);
} }
} }
@@ -97,13 +97,9 @@ public class DestroyBuildingHandler extends AbstractClientMsgHandler {
if (city == null) if (city == null)
return true; return true;
Warehouse.destroyedWarehouses.remove(building.getObjectUUID()); DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
// Rubble no longer owns city.warehouse, which may be null or a newly placed warehouse city.warehouse = null;
if (city.warehouse != null && building.getRank() != -1) {
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
city.warehouse = null;
}
} }
@@ -15,7 +15,7 @@ import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.ManageCityAssetsMsg; import engine.net.client.msg.ManageCityAssetsMsg;
import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.PlaceAssetMsg;
import engine.objects.*; import engine.objects.*;
import org.joda.time.DateTime; import java.time.LocalDateTime;
/* /*
* @Author: * @Author:
@@ -141,9 +141,17 @@ public class ManageCityAssetMsgHandler extends AbstractClientMsgHandler {
return true; return true;
} }
Warehouse warehouse = Warehouse.destroyedWarehouses.get(building.getObjectUUID()); City city = building.getCity();
if (warehouse == null || Warehouse.isEmpty(warehouse)) { if (city == null)
return true;
Warehouse warehouse = city.warehouse;
if (warehouse == null)
return true;
if (Warehouse.isEmpty(warehouse)) {
ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources. ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources.
return true; return true;
} }
@@ -243,11 +251,10 @@ public class ManageCityAssetMsgHandler extends AbstractClientMsgHandler {
return true; return true;
} }
DateTime baneLive = new DateTime(bane.getPlacementDate()); LocalDateTime baneLive = bane.getPlacementDate();
baneLive = baneHour == 24 ? baneLive.plusDays(3) : baneLive.plusDays(2); baneLive = baneHour == 24 ? baneLive.plusDays(3) : baneLive.plusDays(2);
baneLive = baneHour == 24 ? baneLive.hourOfDay().setCopy(0) : baneLive.hourOfDay().setCopy(baneHour); baneLive = baneHour == 24 ? baneLive.withHour(0) : baneLive.withHour(baneHour);
baneLive = baneLive.minuteOfHour().setCopy(0); baneLive = baneLive.withMinute(0).withSecond(1).withNano(0);
baneLive = baneLive.secondOfMinute().setCopy(1);
bane.setLiveDate(baneLive); bane.setLiveDate(baneLive);
outMsg.actionType = 18; outMsg.actionType = 18;
@@ -19,7 +19,7 @@ import engine.net.client.msg.PlaceAssetMsg;
import engine.net.client.msg.PlaceAssetMsg.PlacementInfo; import engine.net.client.msg.PlaceAssetMsg.PlacementInfo;
import engine.objects.*; import engine.objects.*;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import java.time.Instant;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -135,8 +135,6 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) { private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) {
Bane bane;
if (serverZone.guild_zone == false) { if (serverZone.guild_zone == false) {
PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName()); PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName());
return false; return false;
@@ -207,15 +205,6 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
return false; return false;
} }
// Siege equipment has its own validation and never reaches here
bane = city.getBane();
if (bane != null) {
PlaceAssetMsg.sendPlaceAssetError(origin, 74, ""); //"Only buildings of war may be placed during a bane"
return false;
}
if (placementCollisionCheck(serverZone, origin, placementInfo)) { if (placementCollisionCheck(serverZone, origin, placementInfo)) {
PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets
return false; return false;
@@ -1153,7 +1142,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
Blueprint blueprint; Blueprint blueprint;
Building newMesh; Building newMesh;
DateTime completionDate; Instant completionDate;
float vendorRotation; float vendorRotation;
float buildingRotation; float buildingRotation;
@@ -1168,9 +1157,9 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
if ((blueprint.getBuildingGroup().equals(BuildingGroup.SIEGETENT)) if ((blueprint.getBuildingGroup().equals(BuildingGroup.SIEGETENT))
|| (blueprint.getBuildingGroup().equals(BuildingGroup.BULWARK))) || (blueprint.getBuildingGroup().equals(BuildingGroup.BULWARK)))
completionDate = DateTime.now().plusMinutes(15); completionDate = Instant.now().plusSeconds(15 * 60);
else else
completionDate = DateTime.now().plusHours(blueprint.getRankTime(1)); completionDate = Instant.now().plusSeconds(blueprint.getRankTime(1) * 3600);
Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone)); Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));
@@ -1239,7 +1228,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
ArrayList<AbstractGameObject> shrineObjects = DbManager.ShrineQueries.CREATE_SHRINE( ArrayList<AbstractGameObject> shrineObjects = DbManager.ShrineQueries.CREATE_SHRINE(
currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0), currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0, localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation, shrineType.name()); Instant.now().plusSeconds(blueprint.getRankTime(1) * 3600), blueprint.getMeshForRank(0), vendorRotation, buildingRotation, shrineType.name());
if (shrineObjects == null) { if (shrineObjects == null) {
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
@@ -1299,7 +1288,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
float buildingRotation = buildingInfo.getRot().y; float buildingRotation = buildingInfo.getRot().y;
float vendorRotation = buildingInfo.getW(); float vendorRotation = buildingInfo.getW();
DateTime completionDate = DateTime.now().plusHours(blueprint.getRankTime(1)); Instant completionDate = Instant.now().plusSeconds(blueprint.getRankTime(1) * 3600);
newMesh = DbManager.BuildingQueries.CREATE_BUILDING( newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
@@ -1344,7 +1333,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
warehouseBuilding = DbManager.BuildingQueries.CREATE_BUILDING( warehouseBuilding = DbManager.BuildingQueries.CREATE_BUILDING(
currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0), currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0, localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation); Instant.now().plusSeconds(blueprint.getRankTime(1) * 3600), blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
if (warehouseBuilding == null) { if (warehouseBuilding == null) {
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
@@ -1352,7 +1341,6 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
} }
Warehouse warehouse = new Warehouse(warehouseBuilding); Warehouse warehouse = new Warehouse(warehouseBuilding);
Warehouse.destroyedWarehouses.values().removeIf(destroyed -> destroyed.city == city);
city.warehouse = warehouse; city.warehouse = warehouse;
DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse); DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse);
@@ -17,8 +17,8 @@ import engine.net.Protocol;
import engine.objects.Building; import engine.objects.Building;
import engine.objects.Guild; import engine.objects.Guild;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import java.time.LocalDateTime;
import org.joda.time.Seconds; import java.time.Duration;
public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg { public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
@@ -26,8 +26,8 @@ public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
private Building treeOfLife; private Building treeOfLife;
private int currentMineHour; private int currentMineHour;
private Seconds secondsLeft; private Duration secondsLeft;
private DateTime lateTime; private LocalDateTime lateTime;
private int late; private int late;
/** /**
@@ -72,15 +72,15 @@ public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
currentMineHour = guild.getMineTime(); currentMineHour = guild.getMineTime();
late = MBServerStatics.MINE_LATE_WINDOW; late = MBServerStatics.MINE_LATE_WINDOW;
lateTime = DateTime.now(); lateTime = LocalDateTime.now();
if (late == 0) if (late == 0)
lateTime = lateTime.plusDays(1); lateTime = lateTime.plusDays(1);
lateTime = lateTime.hourOfDay().setCopy(late); lateTime = lateTime.withHour(late);
late = ((late > 23) ? (late - 24) : late); late = ((late > 23) ? (late - 24) : late);
secondsLeft = Seconds.secondsBetween(DateTime.now(), lateTime); secondsLeft = Duration.between(LocalDateTime.now(), lateTime);
} }
/** /**
@@ -106,4 +106,4 @@ public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
public void setBuildingUUID(int buildingUUID) { public void setBuildingUUID(int buildingUUID) {
this.buildingUUID = buildingUUID; this.buildingUUID = buildingUUID;
} }
} }
@@ -18,9 +18,6 @@ import engine.net.ByteBufferReader;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import engine.net.Protocol; import engine.net.Protocol;
import engine.objects.*; import engine.objects.*;
import org.joda.time.DateTime;
import org.joda.time.Period;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.Set; import java.util.Set;
@@ -397,37 +394,33 @@ public class ManageCityAssetsMsg extends ClientNetMsg {
else else
writer.put((byte) 0); writer.put((byte) 0);
DateTime placedOn = bane.getLiveDate(); LocalDateTime placedOn = bane.getLiveDate();
if (placedOn == null) if (placedOn == null)
placedOn = new DateTime(DateTime.now()); placedOn = LocalDateTime.now();
//set Calander to date of bane live. //set Calander to date of bane live.
DateTime now = DateTime.now(); LocalDateTime now = LocalDateTime.now();
DateTime defaultTime = new DateTime(bane.getPlacementDate()); LocalDateTime defaultTime = bane.getPlacementDate();
DateTime playerEnterWorldTime = new DateTime(this.assetManager.getTimeStamp("EnterWorld")); LocalDateTime playerEnterWorldTime = LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(this.assetManager.getTimeStamp("EnterWorld")), ZoneId.systemDefault());
Period period = new Period(playerEnterWorldTime.getMillis(), now.getMillis()); int hoursLoggedIn = (int) java.time.Duration.between(playerEnterWorldTime, now).toHours();
int hoursLoggedIn = period.getHours();
hoursLoggedIn = hoursLoggedIn < 0 ? 0 : hoursLoggedIn; hoursLoggedIn = hoursLoggedIn < 0 ? 0 : hoursLoggedIn;
defaultTime = defaultTime.plusDays(2); defaultTime = defaultTime.plusDays(2).withHour(22).withMinute(0).withSecond(0).withNano(0);
defaultTime = defaultTime.hourOfDay().setCopy(22);
defaultTime = defaultTime.minuteOfHour().setCopy(0);
defaultTime = defaultTime.secondOfMinute().setCopy(0);
long curTime = now.getMillis(); long curTime = now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long timeLeft = 0; long timeLeft = 0;
if (bane.getLiveDate() != null) if (bane.getLiveDate() != null)
timeLeft = bane.getLiveDate().getMillis() - curTime; timeLeft = bane.getLiveDate().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - curTime;
else else
timeLeft = defaultTime.getMillis() - curTime + 1000; timeLeft = defaultTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - curTime + 1000;
//DO not touch these. They are static formula's until i get the correct converter for SB Time. //DO not touch these. They are static formula's until i get the correct converter for SB Time.
writer.put((byte) placedOn.dayOfMonth().get()); writer.put((byte) placedOn.getDayOfMonth());
writer.put((byte) placedOn.monthOfYear().get()); writer.put((byte) placedOn.getMonthValue());
writer.putInt(placedOn.year().get() - 1900); writer.putInt(placedOn.getYear() - 1900);
writer.put((byte) 0); writer.put((byte) 0);
writer.put((byte) 0); writer.put((byte) 0);
writer.put((byte) 0); writer.put((byte) 0);
+20 -24
View File
@@ -19,9 +19,8 @@ import engine.net.ByteBufferReader;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import engine.net.Protocol; import engine.net.Protocol;
import engine.objects.*; import engine.objects.*;
import org.joda.time.DateTime; import java.time.Duration;
import org.joda.time.Period; import java.time.LocalDateTime;
import org.joda.time.Seconds;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.util.ArrayList; import java.util.ArrayList;
@@ -137,7 +136,6 @@ public class ManageNPCMsg extends ClientNetMsg {
@Override @Override
protected void _serialize(ByteBufferWriter writer) { protected void _serialize(ByteBufferWriter writer) {
Period upgradePeriod;
int upgradePeriodInSeconds; int upgradePeriodInSeconds;
try { try {
@@ -199,21 +197,20 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(npc.getUpgradeCost()); writer.putInt(npc.getUpgradeCost());
if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(DateTime.now())) long upgradeTotalSecs = 0;
upgradePeriod = new Period(DateTime.now(), npc.getUpgradeDateTime()); if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(LocalDateTime.now()))
else upgradeTotalSecs = Duration.between(LocalDateTime.now(), npc.getUpgradeDateTime()).getSeconds();
upgradePeriod = new Period(0);
writer.put((byte) upgradePeriod.getDays()); //for timer writer.put((byte) (int) (upgradeTotalSecs / 86400)); //for timer
writer.put((byte) unknown26);//unknown writer.put((byte) unknown26);//unknown
writer.putInt(100); //unknown writer.putInt(100); //unknown
writer.put((byte) upgradePeriod.getHours()); //for timer writer.put((byte) (int) ((upgradeTotalSecs % 86400) / 3600)); //for timer
writer.put((byte) upgradePeriod.getMinutes()); //for timer writer.put((byte) (int) ((upgradeTotalSecs % 3600) / 60)); //for timer
writer.put((byte) upgradePeriod.getSeconds()); //for timer writer.put((byte) (int) (upgradeTotalSecs % 60)); //for timer
if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(DateTime.now())) if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(LocalDateTime.now()))
upgradePeriodInSeconds = Seconds.secondsBetween(DateTime.now(), npc.getUpgradeDateTime()).getSeconds(); upgradePeriodInSeconds = (int) upgradeTotalSecs;
else else
upgradePeriodInSeconds = 0; upgradePeriodInSeconds = 0;
@@ -589,21 +586,20 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(Mob.getUpgradeCost(mobA)); writer.putInt(Mob.getUpgradeCost(mobA));
if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(DateTime.now())) long upgradeTotalSecs = 0;
upgradePeriod = new Period(DateTime.now(), mobA.getUpgradeDateTime()); if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(LocalDateTime.now()))
else upgradeTotalSecs = Duration.between(LocalDateTime.now(), mobA.getUpgradeDateTime()).getSeconds();
upgradePeriod = new Period(0);
writer.put((byte) upgradePeriod.getDays()); //for timer writer.put((byte) (int) (upgradeTotalSecs / 86400)); //for timer
writer.put((byte) unknown26);//unknown writer.put((byte) unknown26);//unknown
writer.putInt(100); //unknown writer.putInt(100); //unknown
writer.put((byte) upgradePeriod.getHours()); //for timer writer.put((byte) (int) ((upgradeTotalSecs % 86400) / 3600)); //for timer
writer.put((byte) upgradePeriod.getMinutes()); //for timer writer.put((byte) (int) ((upgradeTotalSecs % 3600) / 60)); //for timer
writer.put((byte) upgradePeriod.getSeconds()); //for timer writer.put((byte) (int) (upgradeTotalSecs % 60)); //for timer
if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(DateTime.now())) if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(LocalDateTime.now()))
upgradePeriodInSeconds = Seconds.secondsBetween(DateTime.now(), mobA.getUpgradeDateTime()).getSeconds(); upgradePeriodInSeconds = (int) upgradeTotalSecs;
else else
upgradePeriodInSeconds = 0; upgradePeriodInSeconds = 0;
@@ -17,7 +17,7 @@ import engine.net.ByteBufferWriter;
import engine.net.Protocol; import engine.net.Protocol;
import engine.net.client.msg.ClientNetMsg; import engine.net.client.msg.ClientNetMsg;
import engine.objects.*; import engine.objects.*;
import org.joda.time.DateTime; import java.time.Instant;
public class GuildInfoMsg extends ClientNetMsg { public class GuildInfoMsg extends ClientNetMsg {
@@ -306,7 +306,7 @@ class GuildInfoMessageType4 extends GuildInfoMessageType {
writer.putString(guildLeaderName); writer.putString(guildLeaderName);
writer.putString(pc.getName()); writer.putString(pc.getName());
writer.putDateTime(DateTime.now()); writer.putDateTime(Instant.now());
writer.put((byte) 1); writer.put((byte) 1);
writer.put((byte) 1); writer.put((byte) 1);
writer.put((byte) 1); writer.put((byte) 1);
@@ -401,7 +401,7 @@ class GuildInfoMessageType5 extends GuildInfoMessageType {
writer.putString("Guild Leader"); writer.putString("Guild Leader");
writer.putString(""); //Nation Leader - I believe writer.putString(""); //Nation Leader - I believe
DateTime now = DateTime.now(); Instant now = Instant.now();
writer.putDateTime(now); writer.putDateTime(now);
writer.put((byte) 1); writer.put((byte) 1);
+10 -90
View File
@@ -656,30 +656,12 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD)); AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD));
if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) { if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
Mob mob = (Mob) abstractCharacter; Mob mob = (Mob) abstractCharacter;
ItemTemplate rune = ItemTemplate.templates.get(mob.loadID); abstractCharacter.minDamageHandOne = (int) mob.mobBase.getDamageMin();
float minDamage = mob.mobBase.getDamageMin(); abstractCharacter.minDamageHandTwo = (int) mob.mobBase.getDamageMin();
float maxDamage = mob.mobBase.getDamageMax(); abstractCharacter.maxDamageHandOne = (int) mob.mobBase.getDamageMax();
int attackRating = mob.mobBase.getAttackRating(); abstractCharacter.maxDamageHandTwo = (int) mob.mobBase.getDamageMax();
abstractCharacter.atrHandOne = mob.mobBase.getAttackRating();
// The client (ArcMobile, 0x46D200) takes mob damage straight from the rune MINDAMAGE/MAXDAMAGE, abstractCharacter.atrHandTwo = mob.mobBase.getAttackRating();
// with no stat scaling. Use the rune when it has damage, otherwise keep the mobbase value.
if (rune != null && rune.rune_max_damage > 0) {
minDamage = rune.rune_min_damage;
maxDamage = rune.rune_max_damage;
}
// Likewise ATR comes straight from the rune ATTACK for mobiles (0x466570).
if (rune != null && rune.rune_attack > 0)
attackRating = rune.rune_attack;
abstractCharacter.minDamageHandOne = (int) minDamage;
abstractCharacter.minDamageHandTwo = (int) minDamage;
abstractCharacter.maxDamageHandOne = (int) maxDamage;
abstractCharacter.maxDamageHandTwo = (int) maxDamage;
abstractCharacter.atrHandOne = attackRating;
abstractCharacter.atrHandTwo = attackRating;
abstractCharacter.defenseRating = mob.mobBase.getDefenseRating(); abstractCharacter.defenseRating = mob.mobBase.getDefenseRating();
} }
} }
@@ -735,18 +717,9 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
mastDam = CharacterSkill.getQuickMastery(abstractCharacter, "Unarmed Combat Mastery"); mastDam = CharacterSkill.getQuickMastery(abstractCharacter, "Unarmed Combat Mastery");
else else
mastDam = masteryPercentage; mastDam = masteryPercentage;
// Base fist damage from the race rune natural attack (1-4 for all races), scaled below like a weapon. // TODO Correct these
// Matches the client's unarmed damage calc (0x46D200). min = 1;
max = 3;
float[] naturalAttack = CombatManager.getNaturalAttack(abstractCharacter, mainHand ? EquipSlotType.RHELD : EquipSlotType.LHELD);
if (naturalAttack != null && naturalAttack.length > 9) {
min = (int) naturalAttack[8];
max = (int) naturalAttack[9];
} else {
min = 1;
max = 3;
}
} else { } else {
if (mainHand) if (mainHand)
@@ -1503,22 +1476,13 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
Mob mob = (Mob) this; Mob mob = (Mob) this;
if (mob.isSiege()) { if (mob.isSiege()) {
return CombatManager.getSiegeRange(mob); return 300;
} }
float range = 8; float range = 8;
if ((this).charItemManager.equipped.get(EquipSlotType.RHELD) != null) { if ((this).charItemManager.equipped.get(EquipSlotType.RHELD) != null) {
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.RHELD).template.item_weapon_max_range; range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.RHELD).template.item_weapon_max_range;
} else if (((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD) != null) { } else if (((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD) != null) {
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD).template.item_weapon_max_range; range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD).template.item_weapon_max_range;
} else {
// Unarmed: use the rune natural attack range when it has one (e.g. Gasser 150), not clamped.
// Most melee runes have 0, so they keep the default of 8.
float naturalRange = CombatManager.getNaturalAttackRange(mob, EquipSlotType.RHELD);
if (naturalRange > 0)
return naturalRange;
} }
// TODO Is this clamp from live? // TODO Is this clamp from live?
@@ -1863,15 +1827,6 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
if (this.getObjectType().equals(GameObjectType.Mob)) { if (this.getObjectType().equals(GameObjectType.Mob)) {
//handle hate value addition //handle hate value addition
Mob target = (Mob) this; Mob target = (Mob) this;
// Siege engines switch to whoever damages them, players and pets alike.
// Misses and non-damaging powers do not pull them.
if (target.isSiege() && value < 0f && attacker != null && !attacker.equals(target)) {
target.setCombatTarget(attacker);
flagSiegeAttackerHostile(target, attacker);
}
if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter)) { if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter)) {
if (target.playerAgroMap.containsKey(attacker.getObjectUUID())) if (target.playerAgroMap.containsKey(attacker.getObjectUUID()))
@@ -1905,41 +1860,6 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
return 0; return 0;
} }
private static void flagSiegeAttackerHostile(Mob siegeEngine, AbstractCharacter attacker) {
// Damaging a siege engine on a city grid flags the player hostile to that
// city, unless they belong to the city's nation or an allied nation.
// Pets flag their owner.
City city;
AbstractCharacter flagged;
Guild cityNation;
city = ZoneManager.getCityAtLocation(siegeEngine.loc);
if (city == null)
return;
flagged = attacker;
if (attacker.getObjectType().equals(GameObjectType.Mob))
flagged = ((Mob) attacker).guardCaptain;
if (flagged == null || !flagged.getObjectType().equals(GameObjectType.PlayerCharacter))
return;
cityNation = city.getGuild().getNation();
if (flagged.getGuild().getNation().equals(cityNation))
return;
if (cityNation.getAllyList().contains(flagged.getGuild().getNation()))
return;
if (!city.cityOutlaws.contains(flagged.getObjectUUID()))
city.cityOutlaws.add(flagged.getObjectUUID());
}
public float getCurrentHitpoints() { public float getCurrentHitpoints() {
return this.health.get(); return this.health.get();
} }
@@ -148,20 +148,9 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
public void dismiss() { public void dismiss() {
boolean isSiegeEngine;
isSiegeEngine = this.getObjectType() == GameObjectType.Mob && ((Mob) this).isSiege();
if (this.isPet()) { if (this.isPet()) {
if (isSiegeEngine) { if ((this.agentType.equals(mbEnums.AIAgentType.PET))) { //delete summoned pet
// Released siege engines stay in place, alive, and keep firing at
// their current target until a Rod of Command takes them again.
this.agentType = mbEnums.AIAgentType.MOBILE;
} else if ((this.agentType.equals(mbEnums.AIAgentType.PET))) { //delete summoned pet
WorldGrid.RemoveWorldObject(this); WorldGrid.RemoveWorldObject(this);
@@ -178,8 +167,7 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
// clear owner and set not alive // clear owner and set not alive
if (!isSiegeEngine) this.isAlive.set(false);
this.isAlive.set(false);
PlayerCharacter owner = (PlayerCharacter) this.guardCaptain; PlayerCharacter owner = (PlayerCharacter) this.guardCaptain;
@@ -200,8 +188,6 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
this.guardCaptain = null; this.guardCaptain = null;
} }
if (isSiegeEngine && this.isAlive())
WorldGrid.updateObject(this);
} }
} }
+18 -22
View File
@@ -25,7 +25,8 @@ import engine.net.client.ClientConnection;
import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.PlaceAssetMsg;
import engine.net.client.msg.chat.ChatSystemMsg; import engine.net.client.msg.chat.ChatSystemMsg;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
@@ -40,8 +41,8 @@ public final class Bane {
private final int cityUUID; private final int cityUUID;
private final int stoneUUID; private final int stoneUUID;
private int ownerUUID; private int ownerUUID;
private DateTime placementDate = null; private LocalDateTime placementDate = null;
private DateTime liveDate = null; private LocalDateTime liveDate = null;
private BaneDefaultTimeJob defaultTimeJob; private BaneDefaultTimeJob defaultTimeJob;
// Internal cache for banes // Internal cache for banes
@@ -62,12 +63,12 @@ public final class Bane {
sqlDateTime = rs.getTimestamp("placementDate"); sqlDateTime = rs.getTimestamp("placementDate");
if (sqlDateTime != null) if (sqlDateTime != null)
this.placementDate = new DateTime(sqlDateTime); this.placementDate = LocalDateTime.ofInstant(sqlDateTime.toInstant(), ZoneId.systemDefault());
sqlDateTime = rs.getTimestamp("liveDate"); sqlDateTime = rs.getTimestamp("liveDate");
if (sqlDateTime != null) if (sqlDateTime != null)
this.liveDate = new DateTime(sqlDateTime); this.liveDate = LocalDateTime.ofInstant(sqlDateTime.toInstant(), ZoneId.systemDefault());
switch (this.getSiegePhase()) { switch (this.getSiegePhase()) {
@@ -81,7 +82,7 @@ public final class Bane {
this.activateBaneJob.cancelJob(); this.activateBaneJob.cancelJob();
abtj = new ActivateBaneJob(cityUUID); abtj = new ActivateBaneJob(cityUUID);
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
this.activateBaneJob = abtj; this.activateBaneJob = abtj;
break; break;
@@ -93,7 +94,7 @@ public final class Bane {
this.activateBaneJob.cancelJob(); this.activateBaneJob.cancelJob();
abtj = new ActivateBaneJob(cityUUID); abtj = new ActivateBaneJob(cityUUID);
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
this.activateBaneJob = abtj; this.activateBaneJob = abtj;
break; break;
@@ -353,7 +354,7 @@ public final class Bane {
return phase; return phase;
} }
if (DateTime.now().isAfter(this.liveDate)) { if (LocalDateTime.now().isAfter(this.liveDate)) {
phase = SiegePhase.WAR; //war phase = SiegePhase.WAR; //war
return phase; return phase;
} }
@@ -368,15 +369,10 @@ public final class Bane {
private void setDefaultTime() { private void setDefaultTime() {
DateTime timeToSetDefault = new DateTime(this.placementDate); LocalDateTime timeToSetDefault = this.placementDate.plusDays(1);
timeToSetDefault = timeToSetDefault.plusDays(1);
DateTime currentTime = DateTime.now(); LocalDateTime currentTime = LocalDateTime.now();
DateTime defaultTime = new DateTime(this.placementDate); LocalDateTime defaultTime = this.placementDate.plusDays(2).withHour(22).withMinute(0).withSecond(0);
defaultTime = defaultTime.plusDays(2);
defaultTime = defaultTime.hourOfDay().setCopy(22);
defaultTime = defaultTime.minuteOfHour().setCopy(0);
defaultTime = defaultTime.secondOfMinute().setCopy(0);
if (currentTime.isAfter(timeToSetDefault)) if (currentTime.isAfter(timeToSetDefault))
this.setLiveDate(defaultTime); this.setLiveDate(defaultTime);
@@ -386,7 +382,7 @@ public final class Bane {
this.defaultTimeJob.cancelJob(); this.defaultTimeJob.cancelJob();
BaneDefaultTimeJob bdtj = new BaneDefaultTimeJob(this); BaneDefaultTimeJob bdtj = new BaneDefaultTimeJob(this);
JobScheduler.getInstance().scheduleJob(bdtj, timeToSetDefault.getMillis()); JobScheduler.getInstance().scheduleJob(bdtj, timeToSetDefault.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
this.defaultTimeJob = bdtj; this.defaultTimeJob = bdtj;
} }
} }
@@ -453,7 +449,7 @@ public final class Bane {
return true; return true;
} }
public final DateTime getPlacementDate() { public final LocalDateTime getPlacementDate() {
return placementDate; return placementDate;
} }
@@ -462,14 +458,14 @@ public final class Bane {
return (this.getSiegePhase() != SiegePhase.CHALLENGE); return (this.getSiegePhase() != SiegePhase.CHALLENGE);
} }
public final DateTime getLiveDate() { public final LocalDateTime getLiveDate() {
return liveDate; return liveDate;
} }
public void setLiveDate(DateTime baneTime) { public void setLiveDate(LocalDateTime baneTime) {
if (DbManager.BaneQueries.SET_BANE_TIME(baneTime, this.getCity().getObjectUUID())) { if (DbManager.BaneQueries.SET_BANE_TIME(baneTime, this.getCity().getObjectUUID())) {
this.liveDate = new DateTime(baneTime); this.liveDate = baneTime;
// Push event to warehouse // Push event to warehouse
@@ -511,7 +507,7 @@ public final class Bane {
ActivateBaneJob abtj = new ActivateBaneJob(cityUUID); ActivateBaneJob abtj = new ActivateBaneJob(cityUUID);
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
this.activateBaneJob = abtj; this.activateBaneJob = abtj;
} else { } else {
Logger.debug("error with city " + this.getCity().getName()); Logger.debug("error with city " + this.getCity().getName());
+1 -4
View File
@@ -395,10 +395,8 @@ public class Building extends AbstractWorldObject {
//friendly building has been attacked, add attacker to city outlaw list //friendly building has been attacked, add attacker to city outlaw list
if (!playerCity.cityOutlaws.contains(attacker.getObjectUUID()) && attacker.getObjectType().equals(GameObjectType.PlayerCharacter)) if (!playerCity.cityOutlaws.contains(attacker.getObjectUUID()) && attacker.getObjectType().equals(GameObjectType.PlayerCharacter))
playerCity.cityOutlaws.add(attacker.getObjectUUID()); playerCity.cityOutlaws.add(attacker.getObjectUUID());
//only city guards respond; siege engines and pets in the zone are left alone
for (Mob guard : playerCity.getParent().zoneMobSet) for (Mob guard : playerCity.getParent().zoneMobSet)
if (guard.isPlayerGuard() && guard.combatTarget == null) if (guard.combatTarget == null)
guard.setCombatTarget(attacker); guard.setCombatTarget(attacker);
} }
} }
@@ -433,7 +431,6 @@ public class Building extends AbstractWorldObject {
// Special handling of warehouses // Special handling of warehouses
if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) { if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) {
Warehouse.destroyedWarehouses.put(this.getObjectUUID(), city.warehouse);
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse); DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
city.warehouse = null; city.warehouse = null;
} }
+3 -3
View File
@@ -22,7 +22,7 @@ import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.UpdateClientAlliancesMsg; import engine.net.client.msg.UpdateClientAlliancesMsg;
import engine.net.client.msg.guild.GuildInfoMsg; import engine.net.client.msg.guild.GuildInfoMsg;
import engine.server.world.WorldServer; import engine.server.world.WorldServer;
import org.joda.time.DateTime; import java.time.Instant;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
@@ -612,8 +612,8 @@ public class Guild extends AbstractWorldObject {
//Add to Guild History //Add to Guild History
if (pc.getGuild() != null) { if (pc.getGuild() != null) {
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(pc.getGuildUUID(), pc, DateTime.now(), historyType)) { if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(pc.getGuildUUID(), pc, Instant.now(), historyType)) {
GuildHistory guildHistory = new GuildHistory(pc.getGuildUUID(), pc.getGuild().name, DateTime.now(), historyType); GuildHistory guildHistory = new GuildHistory(pc.getGuildUUID(), pc.getGuild().name, Instant.now(), historyType);
pc.getGuildHistory().add(guildHistory); pc.getGuildHistory().add(guildHistory);
} }
} }
+8 -7
View File
@@ -13,10 +13,11 @@ package engine.objects;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.mbEnums.GuildHistoryType; import engine.mbEnums.GuildHistoryType;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import org.joda.time.DateTime;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class GuildHistory { public class GuildHistory {
@@ -24,12 +25,12 @@ public class GuildHistory {
private int guildID; private int guildID;
private String guildName; private String guildName;
private DateTime time; private Instant time;
private GuildHistoryType historyType; private GuildHistoryType historyType;
public GuildHistory(int guildID, String guildName, public GuildHistory(int guildID, String guildName,
DateTime dateTime, GuildHistoryType historyType) { Instant dateTime, GuildHistoryType historyType) {
super(); super();
this.guildID = guildID; this.guildID = guildID;
this.guildName = guildName; this.guildName = guildName;
@@ -49,9 +50,9 @@ public class GuildHistory {
sqlDateTime = rs.getTimestamp("historyDate"); sqlDateTime = rs.getTimestamp("historyDate");
if (sqlDateTime != null) if (sqlDateTime != null)
this.time = new DateTime(sqlDateTime); this.time = sqlDateTime.toInstant();
else else
this.time = DateTime.now().minusYears(1); this.time = Instant.now().minus(1, ChronoUnit.YEARS);
this.historyType = GuildHistoryType.valueOf(rs.getString("historyType")); this.historyType = GuildHistoryType.valueOf(rs.getString("historyType"));
} }
@@ -77,11 +78,11 @@ public class GuildHistory {
writer.putDateTime(this.time); writer.putDateTime(this.time);
} }
public DateTime getTime() { public Instant getTime() {
return time; return time;
} }
public void setTime(DateTime time) { public void setTime(Instant time) {
this.time = time; this.time = time;
} }
-26
View File
@@ -90,14 +90,8 @@ public class ItemTemplate {
public final EnumSet<mbEnums.DisciplineType> item_disc_res = EnumSet.noneOf(mbEnums.DisciplineType.class); public final EnumSet<mbEnums.DisciplineType> item_disc_res = EnumSet.noneOf(mbEnums.DisciplineType.class);
public int item_level_req; public int item_level_req;
public mbEnums.SexType item_sex_req; public mbEnums.SexType item_sex_req;
// Lossy: some runes list the same power several times with different
// arguments (a step table, e.g. PET_DISC). Only the last entry is kept.
// engine.parsers.objects.RuneTemplate keeps the full list.
public final HashMap<String, int[]> item_user_power_action = new HashMap<>(); public final HashMap<String, int[]> item_user_power_action = new HashMap<>();
public final HashMap<String, Integer> item_power_grant = new HashMap<>(); public final HashMap<String, Integer> item_power_grant = new HashMap<>();
// Lossy: some runes carry up to 35 power_actions entries per power
// (a step table, e.g. HNT-031). Only the first entry is kept.
// engine.parsers.objects.RuneTemplate keeps the full list.
public final HashMap<String, int[]> item_power_action = new HashMap<>(); public final HashMap<String, int[]> item_power_action = new HashMap<>();
public final HashMap<mbEnums.ResourceType, Integer> item_resource_cost = new HashMap<>(); public final HashMap<mbEnums.ResourceType, Integer> item_resource_cost = new HashMap<>();
public int modTable; public int modTable;
@@ -144,11 +138,6 @@ public class ItemTemplate {
public final EnumSet<mbEnums.MobBehaviourType> rune_group_role_set = EnumSet.noneOf(mbEnums.MobBehaviourType.class); public final EnumSet<mbEnums.MobBehaviourType> rune_group_role_set = EnumSet.noneOf(mbEnums.MobBehaviourType.class);
public boolean rune_renderable = false; public boolean rune_renderable = false;
public int rune_natural_power_attack; public int rune_natural_power_attack;
// One float[] per rune_naturalattacks entry. Same "attack info" struct as a weapon (verified in IDA, see
// MBDATA/docs/client-map.md): [0] attack speed (WEPSPEED), [1] range (MAXRANGE), [2] unknown int,
// [3] equip slot (1 = RHELD, 2 = LHELD), [4] projectile template id, [5] projectile speed, [6] swing anim,
// [7] damage type, [8] min damage, [9] max damage
public final ArrayList<float[]> rune_natural_attacks = new ArrayList<>();
public final HashMap<String, String> rune_sparse_data = new HashMap<>(); public final HashMap<String, String> rune_sparse_data = new HashMap<>();
public final HashMap<String, int[]> rune_skill_adj = new HashMap<>(); public final HashMap<String, int[]> rune_skill_adj = new HashMap<>();
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class); public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
@@ -565,21 +554,6 @@ public class ItemTemplate {
rune_renderable = jsonObject.getBoolean("rune_renderable"); rune_renderable = jsonObject.getBoolean("rune_renderable");
rune_natural_power_attack = jsonObject.getInt("rune_natural_power_attack"); rune_natural_power_attack = jsonObject.getInt("rune_natural_power_attack");
if (jsonObject.has("rune_naturalattacks")) {
JSONArray natural_attacks_json = jsonObject.getJSONArray("rune_naturalattacks");
for (Object attackEntry : natural_attacks_json) {
JSONArray attackArray = (JSONArray) attackEntry;
float[] attack = new float[attackArray.length()];
for (int i = 0; i < attackArray.length(); i++)
attack[i] = attackArray.getFloat(i);
rune_natural_attacks.add(attack);
}
}
JSONObject rune_sparse_json = jsonObject.getJSONObject("rune_sparse_data"); JSONObject rune_sparse_json = jsonObject.getJSONObject("rune_sparse_data");
for (String sparseType : rune_sparse_json.keySet()) { for (String sparseType : rune_sparse_json.keySet()) {
+11 -45
View File
@@ -24,7 +24,8 @@ import engine.net.client.msg.PetMsg;
import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.PlaceAssetMsg;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
@@ -71,7 +72,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
public long nextAttackTime = System.currentTimeMillis(); public long nextAttackTime = System.currentTimeMillis();
private int lastMobPowerToken = 0; private int lastMobPowerToken = 0;
private DeferredPowerJob weaponPower; private DeferredPowerJob weaponPower;
private DateTime upgradeDateTime = null; private LocalDateTime upgradeDateTime = null;
private boolean lootSync = false; private boolean lootSync = false;
// New Mobile constructor. Fill in the blanks and then call // New Mobile constructor. Fill in the blanks and then call
@@ -126,7 +127,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
sqlDateTime = rs.getTimestamp("upgradeDate"); sqlDateTime = rs.getTimestamp("upgradeDate");
if (sqlDateTime != null) if (sqlDateTime != null)
upgradeDateTime = new DateTime(sqlDateTime); upgradeDateTime = LocalDateTime.ofInstant(sqlDateTime.toInstant(), ZoneId.systemDefault());
else else
upgradeDateTime = null; upgradeDateTime = null;
@@ -436,30 +437,6 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
return mobile; return mobile;
} }
// MB_MINION_SPAWN_TIME (seconds) shortens guard/siege minion spawns for testing; 0 or blank keeps the normal timers.
private static int minionSpawnDelay(int defaultDelay) {
String overrideValue;
int overrideDelay;
overrideValue = ConfigManager.MB_MINION_SPAWN_TIME.getValue();
if (overrideValue == null || overrideValue.trim().isEmpty())
return defaultDelay;
try {
overrideDelay = Integer.parseInt(overrideValue.trim());
} catch (NumberFormatException e) {
Logger.warn("MB_MINION_SPAWN_TIME is not a number: " + overrideValue);
return defaultDelay;
}
if (overrideDelay <= 0)
return defaultDelay;
return overrideDelay;
}
public static synchronized Mob createGuardMinion(Mob guardCaptain, short level, String minionName) { public static synchronized Mob createGuardMinion(Mob guardCaptain, short level, String minionName) {
Mob minionMobile; Mob minionMobile;
@@ -479,7 +456,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
minionMobile.deathTime = System.currentTimeMillis(); minionMobile.deathTime = System.currentTimeMillis();
minionMobile.guardCaptain = guardCaptain; minionMobile.guardCaptain = guardCaptain;
minionMobile.spawnDelay = minionSpawnDelay((int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60); minionMobile.spawnDelay = (int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60;
minionMobile.behaviourType = mbEnums.MobBehaviourType.GuardMinion; minionMobile.behaviourType = mbEnums.MobBehaviourType.GuardMinion;
minionMobile.agentType = AIAgentType.GUARDMINION; minionMobile.agentType = AIAgentType.GUARDMINION;
minionMobile.guardedCity = guardCaptain.guardedCity; minionMobile.guardedCity = guardCaptain.guardedCity;
@@ -538,7 +515,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
siegeMinion.behaviourType = MobBehaviourType.SiegeEngine; siegeMinion.behaviourType = MobBehaviourType.SiegeEngine;
siegeMinion.agentType = AIAgentType.SIEGEENGINE; siegeMinion.agentType = AIAgentType.SIEGEENGINE;
siegeMinion.bindLoc = Vector3fImmutable.ZERO; siegeMinion.bindLoc = Vector3fImmutable.ZERO;
siegeMinion.spawnDelay = minionSpawnDelay(60 * 15); siegeMinion.spawnDelay = (60 * 15);
siegeMinion.runAfterLoad(); siegeMinion.runAfterLoad();
@@ -576,12 +553,6 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
petMinion.despawned = false; petMinion.despawned = false;
petMinion.runAfterLoad(); petMinion.runAfterLoad();
// Summoned pets carry MASK_PET. Necro pets keep their own NecroPet handling.
if (!petMinion.isNecroPet())
petMinion.setObjectTypeMask(petMinion.getObjectTypeMask() | MBServerStatics.MASK_PET);
DbManager.addToCache(petMinion); DbManager.addToCache(petMinion);
petMinion.setLoc(petMinion.bindLoc); petMinion.setLoc(petMinion.bindLoc);
@@ -623,8 +594,8 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
// Submit upgrade job for future date or current instant // Submit upgrade job for future date or current instant
if (mob.getUpgradeDateTime().isAfter(DateTime.now())) if (mob.getUpgradeDateTime().isAfter(LocalDateTime.now()))
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), mob.getUpgradeDateTime().getMillis()); JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), mob.getUpgradeDateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
else else
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), 0); JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), 0);
@@ -650,7 +621,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
return upgradeCost; return upgradeCost;
} }
public static void setUpgradeDateTime(Mob mob, DateTime upgradeDateTime) { public static void setUpgradeDateTime(Mob mob, LocalDateTime upgradeDateTime) {
if (!DbManager.MobQueries.updateUpgradeTime(mob, upgradeDateTime)) { if (!DbManager.MobQueries.updateUpgradeTime(mob, upgradeDateTime)) {
Logger.error("Failed to set upgradeTime for building " + mob.currentID); Logger.error("Failed to set upgradeTime for building " + mob.currentID);
@@ -1348,11 +1319,6 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter)) if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter))
return; return;
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth
if (this.isSiege())
return;
if (this.getCombatTarget() == null) if (this.getCombatTarget() == null)
this.setCombatTarget(ac); this.setCombatTarget(ac);
@@ -1388,7 +1354,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
this.weaponPower = weaponPower; this.weaponPower = weaponPower;
} }
public DateTime getUpgradeDateTime() { public LocalDateTime getUpgradeDateTime() {
lock.readLock().lock(); lock.readLock().lock();
@@ -1468,7 +1434,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
if (!building.transferGold(-rankCost, false)) if (!building.transferGold(-rankCost, false))
return; return;
DateTime dateToUpgrade = DateTime.now().plusHours(Mob.getUpgradeTime(this)); LocalDateTime dateToUpgrade = LocalDateTime.now().plusHours(Mob.getUpgradeTime(this));
Mob.setUpgradeDateTime(this, dateToUpgrade); Mob.setUpgradeDateTime(this, dateToUpgrade);
// Schedule upgrade job // Schedule upgrade job
+10 -9
View File
@@ -24,7 +24,8 @@ import engine.net.Dispatch;
import engine.net.client.msg.PetMsg; import engine.net.client.msg.PetMsg;
import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.PlaceAssetMsg;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import java.time.LocalDateTime;
import java.time.ZoneId;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
@@ -67,7 +68,7 @@ public class NPC extends AbstractCharacter {
protected int symbol; protected int symbol;
// Variables NOT to be stored in db // Variables NOT to be stored in db
protected boolean isStatic = false; protected boolean isStatic = false;
private DateTime upgradeDateTime = null; private LocalDateTime upgradeDateTime = null;
private HashSet<Integer> canRoll = null; private HashSet<Integer> canRoll = null;
public int parentZoneUUID; public int parentZoneUUID;
public int equipmentSetID = 0; public int equipmentSetID = 0;
@@ -131,7 +132,7 @@ public class NPC extends AbstractCharacter {
sqlDateTime = rs.getTimestamp("upgradeDate"); sqlDateTime = rs.getTimestamp("upgradeDate");
if (sqlDateTime != null) if (sqlDateTime != null)
upgradeDateTime = new DateTime(sqlDateTime); upgradeDateTime = LocalDateTime.ofInstant(sqlDateTime.toInstant(), ZoneId.systemDefault());
else else
upgradeDateTime = null; upgradeDateTime = null;
@@ -527,9 +528,9 @@ public class NPC extends AbstractCharacter {
// Submit upgrade job for future date or current instant // Submit upgrade job for future date or current instant
if (this.getUpgradeDateTime().isAfter(DateTime.now())) if (this.getUpgradeDateTime().isAfter(LocalDateTime.now()))
jc = JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), jc = JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this),
this.getUpgradeDateTime().getMillis()); this.getUpgradeDateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
else else
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), 0); JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), 0);
@@ -907,7 +908,7 @@ public class NPC extends AbstractCharacter {
return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
} }
public DateTime getUpgradeDateTime() { public LocalDateTime getUpgradeDateTime() {
lock.readLock().lock(); lock.readLock().lock();
try { try {
return upgradeDateTime; return upgradeDateTime;
@@ -916,7 +917,7 @@ public class NPC extends AbstractCharacter {
} }
} }
public void setUpgradeDateTime(DateTime upgradeDateTime) { public void setUpgradeDateTime(LocalDateTime upgradeDateTime) {
if (!DbManager.NPCQueries.updateUpgradeTime(this, upgradeDateTime)) { if (!DbManager.NPCQueries.updateUpgradeTime(this, upgradeDateTime)) {
Logger.error("Failed to set upgradeTime for building " + currentID); Logger.error("Failed to set upgradeTime for building " + currentID);
@@ -1059,7 +1060,7 @@ public class NPC extends AbstractCharacter {
int rankCost; int rankCost;
Building building; Building building;
DateTime dateToUpgrade; LocalDateTime dateToUpgrade;
this.lock.writeLock().lock(); this.lock.writeLock().lock();
@@ -1102,7 +1103,7 @@ public class NPC extends AbstractCharacter {
if (!building.transferGold(-rankCost, false)) if (!building.transferGold(-rankCost, false))
return; return;
dateToUpgrade = DateTime.now().plusHours(this.getUpgradeTime()); dateToUpgrade = LocalDateTime.now().plusHours(this.getUpgradeTime());
this.setUpgradeDateTime(dateToUpgrade); this.setUpgradeDateTime(dateToUpgrade);
+7 -6
View File
@@ -37,7 +37,8 @@ import engine.server.MBServerStatics;
import engine.server.login.LoginServer; import engine.server.login.LoginServer;
import engine.server.world.WorldServer; import engine.server.world.WorldServer;
import engine.util.MiscUtils; import engine.util.MiscUtils;
import org.joda.time.DateTime; import java.time.Instant;
import java.time.ZoneId;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
@@ -702,11 +703,11 @@ public class PlayerCharacter extends AbstractCharacter {
writer.putInt(token); writer.putInt(token);
writer.putInt((int) timeLeft / 1000); writer.putInt((int) timeLeft / 1000);
} }
DateTime enterWorld = new DateTime(playerCharacter.timestamps.get("EnterWorld")); Instant enterWorld = Instant.ofEpochMilli(playerCharacter.timestamps.get("EnterWorld"));
writer.putDateTime(enterWorld); writer.putDateTime(enterWorld);
writer.putInt(0x49EF1E98); //DUnno what playerCharacter is. writer.putInt(0x49EF1E98); //DUnno what playerCharacter is.
writer.putFloat(DateTime.now().hourOfDay().get()); //daylight in float. writer.putFloat(Instant.now().atZone(ZoneId.systemDefault()).toLocalTime().getHour()); //daylight in float.
writer.putFloat(6); //interval of light to change per game hour //float writer.putFloat(6); //interval of light to change per game hour //float
//writer.putInt(1637194901); //playerCharacter is actually an opcode taht is in recordings, no clue what it is, dumped it and it changes nothing //writer.putInt(1637194901); //playerCharacter is actually an opcode taht is in recordings, no clue what it is, dumped it and it changes nothing
} else { } else {
@@ -4184,9 +4185,8 @@ public class PlayerCharacter extends AbstractCharacter {
} else if (currentPet.isSiege()) { } else if (currentPet.isSiege()) {
currentPet.agentType = AIAgentType.MOBILE; currentPet.agentType = AIAgentType.MOBILE;
// released siege engine keeps its current target
currentPet.guardCaptain = null; currentPet.guardCaptain = null;
currentPet.setCombatTarget(null);
if (currentPet.isAlive()) if (currentPet.isAlive())
WorldGrid.updateObject(currentPet); WorldGrid.updateObject(currentPet);
} }
@@ -4194,6 +4194,7 @@ public class PlayerCharacter extends AbstractCharacter {
toCommand.setPet(this, false); toCommand.setPet(this, false);
this.setPet(toCommand); this.setPet(toCommand);
toCommand.setCombatTarget(null);
PetMsg petMsg = new PetMsg(6, toCommand); PetMsg petMsg = new PetMsg(6, toCommand);
Dispatch dispatch = Dispatch.borrow(this, petMsg); Dispatch dispatch = Dispatch.borrow(this, petMsg);
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
@@ -4364,7 +4365,7 @@ public class PlayerCharacter extends AbstractCharacter {
} }
if (count > 0) { if (count > 0) {
this.timestamps.put("RealmClaim", DateTime.now().plusMinutes(3).getMillis()); this.timestamps.put("RealmClaim", Instant.now().plusSeconds(180).toEpochMilli());
for (PlayerCharacter toSend : SessionManager.getAllActivePlayerCharacters()) { for (PlayerCharacter toSend : SessionManager.getAllActivePlayerCharacters()) {
ChatManager.chatSystemInfo(toSend, this.getCombinedName() + " is seeking to claim a realm and already has " + count + " blessngs!"); ChatManager.chatSystemInfo(toSend, this.getCombinedName() + " is seeking to claim a realm and already has " + count + " blessngs!");
} }
+6 -6
View File
@@ -12,7 +12,7 @@ package engine.objects;
import engine.mbEnums; import engine.mbEnums;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.mbEnums.TransactionType; import engine.mbEnums.TransactionType;
import org.joda.time.DateTime; import java.time.Instant;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -24,7 +24,7 @@ public class Transaction implements Comparable<Transaction> {
private final int warehouseUUID; private final int warehouseUUID;
private final int targetUUID; private final int targetUUID;
private final mbEnums.ResourceType resource; private final mbEnums.ResourceType resource;
private final DateTime date; private final Instant date;
private final int amount; private final int amount;
private final TransactionType transactionType; private final TransactionType transactionType;
private GameObjectType targetType; private GameObjectType targetType;
@@ -41,15 +41,15 @@ public class Transaction implements Comparable<Transaction> {
Date sqlDateTime = rs.getTimestamp("date"); Date sqlDateTime = rs.getTimestamp("date");
if (sqlDateTime != null) if (sqlDateTime != null)
this.date = new DateTime(sqlDateTime); this.date = sqlDateTime.toInstant();
else else
this.date = DateTime.now(); this.date = Instant.now();
} }
public Transaction(int warehouseUUID, GameObjectType targetType, int targetUUID, TransactionType transactionType, mbEnums.ResourceType resource, int amount, public Transaction(int warehouseUUID, GameObjectType targetType, int targetUUID, TransactionType transactionType, mbEnums.ResourceType resource, int amount,
DateTime date) { Instant date) {
this.warehouseUUID = warehouseUUID; this.warehouseUUID = warehouseUUID;
this.targetUUID = targetUUID; this.targetUUID = targetUUID;
this.resource = resource; this.resource = resource;
@@ -75,7 +75,7 @@ public class Transaction implements Comparable<Transaction> {
} }
public DateTime getDate() { public Instant getDate() {
return date; return date;
} }
+4 -7
View File
@@ -18,7 +18,7 @@ import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.*; import engine.net.client.msg.*;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import java.time.Instant;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -31,9 +31,6 @@ import java.util.concurrent.ConcurrentHashMap;
public class Warehouse { public class Warehouse {
// Destroyed warehouses keyed by rubble building UUID; memory only so the rubble can be looted
public static final ConcurrentHashMap<Integer, Warehouse> destroyedWarehouses = new ConcurrentHashMap<>();
public EnumSet<mbEnums.ResourceType> locked = EnumSet.noneOf(mbEnums.ResourceType.class); public EnumSet<mbEnums.ResourceType> locked = EnumSet.noneOf(mbEnums.ResourceType.class);
public Building building; public Building building;
public City city; public City city;
@@ -501,7 +498,7 @@ public class Warehouse {
if (playerCharacter == null) if (playerCharacter == null)
return false; return false;
ItemTemplate template = ItemTemplate.templates.get(resourceType.templateID); ItemTemplate template = ItemTemplate.templates.get(resourceType);
if (template == null) if (template == null)
return false; return false;
@@ -579,10 +576,10 @@ public class Warehouse {
public static void AddTransactionToWarehouse(Warehouse warehouse, mbEnums.GameObjectType targetType, int targetUUID, mbEnums.TransactionType transactionType, mbEnums.ResourceType resource, int amount) { public static void AddTransactionToWarehouse(Warehouse warehouse, mbEnums.GameObjectType targetType, int targetUUID, mbEnums.TransactionType transactionType, mbEnums.ResourceType resource, int amount) {
if (!DbManager.WarehouseQueries.CREATE_TRANSACTION(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, DateTime.now())) if (!DbManager.WarehouseQueries.CREATE_TRANSACTION(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, Instant.now()))
return; return;
Transaction transaction = new Transaction(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, DateTime.now()); Transaction transaction = new Transaction(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, Instant.now());
warehouse.transactions.add(transaction); warehouse.transactions.add(transaction);
} }
-235
View File
@@ -1,235 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.mbEnums;
import engine.parsers.objects.CObjectTemplate;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.ItemTemplate;
import engine.parsers.objects.PowerGrant;
import engine.parsers.objects.RuneTemplate;
import engine.parsers.objects.UserPowerAction;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
// Lookups across the ITEM, DEED and RUNE cache templates.
// The JSON references runes, skills and powers by name; the server and DB use ids and tokens.
// Build once after all three parsers have loaded. Read only afterwards.
public class CObjectIndex {
private static final EnumMap<mbEnums.MonsterType, ArrayList<Integer>> raceRuneIds = new EnumMap<>(mbEnums.MonsterType.class);
private static final EnumMap<mbEnums.ClassType, Integer> classRuneIds = new EnumMap<>(mbEnums.ClassType.class);
private static final EnumMap<mbEnums.DisciplineType, Integer> disciplineRuneIds = new EnumMap<>(mbEnums.DisciplineType.class);
private static final HashMap<Integer, ArrayList<RuneTemplate>> runesBySubTypeToken = new HashMap<>();
private static final HashMap<String, Integer> skillTokens = new HashMap<>();
private static final HashMap<Integer, String> skillNames = new HashMap<>();
private static final HashMap<String, Integer> powerTokens = new HashMap<>();
private static final HashMap<Integer, String> powerNames = new HashMap<>();
// Same order as the client's id space checks (JSON-Refactor-Plan D1).
public static CObjectTemplate lookup(int templateId) {
CObjectTemplate template;
template = ItemTemplate._items.get(templateId);
if (template == null)
template = DeedTemplate._deeds.get(templateId);
if (template == null)
template = RuneTemplate._runes.get(templateId);
return template;
}
// Male and female runes share a race, so most races return two ids.
public static ArrayList<Integer> getRaceRuneIds(mbEnums.MonsterType race) {
return raceRuneIds.getOrDefault(race, new ArrayList<>());
}
public static Integer getClassRuneId(mbEnums.ClassType classType) {
return classRuneIds.get(classType);
}
public static Integer getDisciplineRuneId(mbEnums.DisciplineType discipline) {
return disciplineRuneIds.get(discipline);
}
// DB race and class tokens are SBStringHash(rune_sub_type). Mob and talent runes share sub types.
public static ArrayList<RuneTemplate> getRunesBySubTypeToken(int token) {
return runesBySubTypeToken.getOrDefault(token, new ArrayList<>());
}
public static Integer getSkillToken(String skillName) {
return skillTokens.get(skillName);
}
public static String getSkillName(int token) {
return skillNames.get(token);
}
public static Integer getPowerToken(String powerName) {
return powerTokens.get(powerName);
}
public static String getPowerName(int token) {
return powerNames.get(token);
}
public static void build() {
int overlapCount = 0;
for (Integer templateId : RuneTemplate._runes.keySet())
if (ItemTemplate._items.containsKey(templateId) || DeedTemplate._deeds.containsKey(templateId))
overlapCount++;
for (Integer templateId : DeedTemplate._deeds.keySet())
if (ItemTemplate._items.containsKey(templateId))
overlapCount++;
if (overlapCount > 0)
Logger.warn(overlapCount + " template ids exist in more than one of ITEM/DEED/RUNE");
for (RuneTemplate rune : RuneTemplate._runes.values()) {
indexRuneName(rune);
indexSubTypeToken(rune);
indexRuneNames(rune);
}
for (ItemTemplate item : ItemTemplate._items.values()) {
addSkill(item.item_skill_used);
addSkill(item.item_skill_mastery_used);
indexItemFields(item.item_skill_req, item.item_user_power_action, item.item_power_grant, item.item_power_action);
}
for (DeedTemplate deed : DeedTemplate._deeds.values()) {
addSkill(deed.item_skill_used);
addSkill(deed.item_skill_mastery_used);
indexItemFields(deed.item_skill_req, deed.item_user_power_action, deed.item_power_grant, deed.item_power_action);
}
Logger.info("Indexed " + raceRuneIds.size() + " races, " + classRuneIds.size() + " classes, "
+ disciplineRuneIds.size() + " disciplines, " + runesBySubTypeToken.size() + " rune sub type tokens, "
+ skillTokens.size() + " skills, " + powerTokens.size() + " powers");
}
private static void indexRuneName(RuneTemplate rune) {
Integer previousId;
if (rune.rune_type == null || rune.rune_sub_type.isEmpty())
return;
try {
switch (rune.rune_type) {
case RACE:
// Mob runes reuse race sub types; only creation races map to player race ids
if (rune.rune_is_standard_character_creation)
raceRuneIds.computeIfAbsent(CacheParserUtil.readEnum(mbEnums.MonsterType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"),
k -> new ArrayList<>()).add(rune.template_id);
break;
case CLASS:
previousId = classRuneIds.put(CacheParserUtil.readEnum(mbEnums.ClassType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"), rune.template_id);
if (previousId != null)
Logger.warn(rune.template_id + ": class " + rune.rune_sub_type + " already indexed by " + previousId);
break;
case DISCIPLINE:
previousId = disciplineRuneIds.put(CacheParserUtil.readEnum(mbEnums.DisciplineType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"), rune.template_id);
if (previousId != null)
Logger.warn(rune.template_id + ": discipline " + rune.rune_sub_type + " already indexed by " + previousId);
break;
default:
break;
}
} catch (IllegalArgumentException e) {
// The Pet class rune has no ClassType
Logger.warn(e.getMessage());
}
}
private static void indexSubTypeToken(RuneTemplate rune) {
int token;
if (!rune.rune_sub_type.isEmpty())
token = Hasher.SBStringHash(rune.rune_sub_type);
else if (rune.rune_sub_type_hash != 0)
token = rune.rune_sub_type_hash;
else
return;
runesBySubTypeToken.computeIfAbsent(token, k -> new ArrayList<>()).add(rune);
}
private static void indexRuneNames(RuneTemplate rune) {
for (RuneTemplate.SkillGrant grant : rune.rune_skill_grant) {
addSkill(grant.skill_type);
grant.skill_granted_skills.keySet().forEach(CObjectIndex::addSkill);
grant.skill_granted_powers.keySet().forEach(CObjectIndex::addPower);
}
rune.rune_skill_adj.keySet().forEach(CObjectIndex::addSkill);
addSkill(rune.item_skill_used);
addSkill(rune.item_skill_mastery_used);
indexItemFields(rune.item_skill_req, rune.item_user_power_action, rune.item_power_grant, rune.item_power_action);
}
private static void indexItemFields(Map<String, Integer> skillReq, ArrayList<UserPowerAction> userPowerActions,
ArrayList<PowerGrant> powerGrants, Map<String, ArrayList<int[]>> powerActions) {
skillReq.keySet().forEach(CObjectIndex::addSkill);
for (UserPowerAction action : userPowerActions)
addPower(action.power);
for (PowerGrant grant : powerGrants) {
addPower(grant.power_type);
grant.power_granted_skills.keySet().forEach(CObjectIndex::addSkill);
grant.power_granted_powers.keySet().forEach(CObjectIndex::addPower);
}
powerActions.keySet().forEach(CObjectIndex::addPower);
}
private static void addSkill(String skillName) {
addName(skillName, skillTokens, skillNames, "skill");
}
private static void addPower(String powerName) {
addName(powerName, powerTokens, powerNames, "power");
}
private static void addName(String name, HashMap<String, Integer> tokens, HashMap<Integer, String> names, String kind) {
int token;
String previousName;
// readHashLabel keeps unresolved hashes as decimal strings; those are not names
if (name == null || name.isEmpty() || name.matches("-?\\d+") || tokens.containsKey(name))
return;
token = Hasher.SBStringHash(name);
tokens.put(name, token);
previousName = names.putIfAbsent(token, name);
if (previousName != null)
Logger.warn(kind + " token " + token + " shared by '" + previousName + "' and '" + name + "'");
}
}
-353
View File
@@ -1,353 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.math.Vector3fImmutable;
import engine.parsers.objects.PowerGrant;
import engine.parsers.objects.UserPowerAction;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Shared JSON helpers for the COBJECTS cache parsers.
// Only generic reading lives here; each parser owns its own field mapping.
public class CacheParserUtil {
// Files are named <template id>.json
public static File[] listJsonFiles(String folderPath) {
File folder = new File(folderPath);
File[] jsonFiles = folder.listFiles((dir, name) -> name.endsWith(".json") && hasTemplateId(name));
if (jsonFiles == null) {
Logger.error("Cache folder not found: " + folder.getAbsolutePath());
return new File[0];
}
return jsonFiles;
}
// Template ids are 32-bit. The client cache has RUNE/10788957847552, which is
// 2512 << 32 (the Huntress id in the high word); files like it are skipped.
private static boolean hasTemplateId(String fileName) {
String idText = fileName.substring(0, fileName.length() - ".json".length());
try {
Integer.parseInt(idText);
return true;
} catch (NumberFormatException e) {
Logger.warn("Skipping " + fileName + ": name is not a 32-bit template id");
return false;
}
}
public static int templateIdFromFile(File jsonFile) {
String fileName = jsonFile.getName();
return Integer.parseInt(fileName.substring(0, fileName.length() - ".json".length()));
}
public static JSONObject readJsonFile(File jsonFile) throws IOException {
byte[] fileData = Files.readAllBytes(jsonFile.toPath());
return new JSONObject(new String(fileData, StandardCharsets.UTF_8));
}
// [x, y, z] -> vector. Returns null for an empty array.
public static Vector3fImmutable readVector3(JSONArray vectorJson) {
if (vectorJson.length() < 3)
return null;
return new Vector3fImmutable(vectorJson.getFloat(0), vectorJson.getFloat(1), vectorJson.getFloat(2));
}
// Client strings carry spaces, hyphens and commas that enum constants
// can't ("Half-Giant", "Battle Magus").
public static String enumName(String value) {
String name = value;
name = name.replaceAll("\\s", "");
name = name.replaceAll("-", "");
name = name.replaceAll(",", "");
return name;
}
// Exact match on the cleaned name first, then upper case (ResourceType).
// Unknown values throw so the template fails to load.
public static <E extends Enum<E>> E readEnum(Class<E> enumClass, String value, int templateId, String fieldName) {
String name = enumName(value);
try {
return Enum.valueOf(enumClass, name);
} catch (IllegalArgumentException e) {
// fall through to upper case
}
try {
return Enum.valueOf(enumClass, name.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(templateId + ": " + fieldName + " has no " + enumClass.getSimpleName() + " for '" + value + "'");
}
}
public static <E extends Enum<E>> EnumSet<E> readEnumSet(Class<E> enumClass, JSONArray valuesJson, int templateId, String fieldName) {
EnumSet<E> enumSet = EnumSet.noneOf(enumClass);
for (int i = 0; i < valuesJson.length(); i++)
enumSet.add(readEnum(enumClass, valuesJson.getString(i), templateId, fieldName));
return enumSet;
}
// [{keyName: "Strength", valueName: 40}, ...] -> enum map
public static <E extends Enum<E>> EnumMap<E, Integer> readEnumValueMap(Class<E> enumClass, JSONArray entriesJson,
String keyName, String valueName,
int templateId, String fieldName) {
EnumMap<E, Integer> enumMap = new EnumMap<>(enumClass);
JSONObject entry;
for (int i = 0; i < entriesJson.length(); i++) {
entry = entriesJson.getJSONObject(i);
enumMap.put(readEnum(enumClass, entry.getString(keyName), templateId, fieldName), entry.getInt(valueName));
}
return enumMap;
}
// [{keyName: "Sword", valueName: 20}, ...] -> map in file order
public static LinkedHashMap<String, Integer> readStringValueMap(JSONArray entriesJson, String keyName, String valueName) {
LinkedHashMap<String, Integer> valueMap = new LinkedHashMap<>();
JSONObject entry;
for (int i = 0; i < entriesJson.length(); i++) {
entry = entriesJson.getJSONObject(i);
valueMap.put(entry.getString(keyName), (int) entry.getLong(valueName));
}
return valueMap;
}
// {"SLASHING": 0.0, ...} -> enum map
public static <E extends Enum<E>> EnumMap<E, Float> readEnumFloatMap(Class<E> enumClass, JSONObject mapJson,
int templateId, String fieldName) {
EnumMap<E, Float> enumMap = new EnumMap<>(enumClass);
for (String key : mapJson.keySet())
enumMap.put(readEnum(enumClass, key, templateId, fieldName), mapJson.getFloat(key));
return enumMap;
}
// Hash and flag fields are still unsigned in the JSON; read as long and wrapped to int.
public static int[] readIntArray(JSONArray valuesJson) {
int[] values = new int[valuesJson.length()];
for (int i = 0; i < valuesJson.length(); i++)
values[i] = (int) valuesJson.getLong(i);
return values;
}
public static float[] readFloatArray(JSONArray valuesJson) {
float[] values = new float[valuesJson.length()];
for (int i = 0; i < valuesJson.length(); i++)
values[i] = valuesJson.getFloat(i);
return values;
}
public static ArrayList<Integer> readIntList(JSONArray valuesJson) {
ArrayList<Integer> values = new ArrayList<>();
for (int i = 0; i < valuesJson.length(); i++)
values.add((int) valuesJson.getLong(i));
return values;
}
// [[a, b], [a, b], ...] -> list of int[]
public static ArrayList<int[]> readIntArrays(JSONArray arraysJson) {
ArrayList<int[]> arrays = new ArrayList<>();
for (int i = 0; i < arraysJson.length(); i++)
arrays.add(readIntArray(arraysJson.getJSONArray(i)));
return arrays;
}
// Sparse data values are mixed bool/int/string; stored as strings.
public static HashMap<String, String> readSparseData(JSONObject sparseJson) {
HashMap<String, String> sparseData = new HashMap<>();
for (String key : sparseJson.keySet())
sparseData.put(key, sparseJson.get(key).toString());
return sparseData;
}
// Some fields are a name when set and the number 0 when not
// (obj_sound_table, item_skill_used). The number becomes "".
public static String readLabel(JSONObject json, String key) {
Object value = json.get(key);
if (value instanceof String)
return (String) value;
return "";
}
// Like readLabel, but a non-zero number is a hash the exporter could not
// name; it is kept as its decimal string rather than dropped.
public static String readHashLabel(JSONObject json, String key, int templateId) {
Object value = json.get(key);
long hash;
if (value instanceof String)
return (String) value;
hash = json.getLong(key);
if (hash == 0)
return "";
Logger.warn(templateId + ": " + key + " has unresolved hash " + hash);
return Long.toString(hash);
}
public static ArrayList<UserPowerAction> readUserPowerActions(JSONArray actionsJson) {
ArrayList<UserPowerAction> userPowerActions = new ArrayList<>();
JSONObject entry;
for (int i = 0; i < actionsJson.length(); i++) {
entry = actionsJson.getJSONObject(i);
userPowerActions.add(new UserPowerAction(entry.getString("power"), readIntArray(entry.getJSONArray("arguments"))));
}
return userPowerActions;
}
// [[key, value], ...] -> map in file order. Keys are names when the exporter
// knows the hash, otherwise the raw hash, which is kept as its decimal string.
public static LinkedHashMap<String, Integer> readGrantPairs(JSONArray pairsJson, int templateId, String fieldName) {
LinkedHashMap<String, Integer> grantPairs = new LinkedHashMap<>();
JSONArray pair;
Object key;
String keyName;
for (int i = 0; i < pairsJson.length(); i++) {
pair = pairsJson.getJSONArray(i);
key = pair.get(0);
if (key instanceof String) {
keyName = (String) key;
} else {
keyName = Long.toString(pair.getLong(0));
Logger.warn(templateId + ": " + fieldName + " has unresolved hash " + keyName);
}
grantPairs.put(keyName, pair.getInt(1));
}
return grantPairs;
}
public static ArrayList<PowerGrant> readPowerGrants(JSONArray grantsJson, int templateId) {
ArrayList<PowerGrant> powerGrants = new ArrayList<>();
JSONObject entry;
PowerGrant powerGrant;
for (int i = 0; i < grantsJson.length(); i++) {
entry = grantsJson.getJSONObject(i);
powerGrant = new PowerGrant();
powerGrant.power_type = entry.getString("power_type");
powerGrant.power_value = entry.getInt("power_value");
powerGrant.power_granted_attrs.putAll(readGrantPairs(entry.getJSONArray("power_granted_attrs"), templateId, "power_granted_attrs"));
powerGrant.power_granted_skills.putAll(readGrantPairs(entry.getJSONArray("power_granted_skills"), templateId, "power_granted_skills"));
powerGrant.power_granted_powers.putAll(readGrantPairs(entry.getJSONArray("power_granted_powers"), templateId, "power_granted_powers"));
powerGrant.power_monster_types = entry.getJSONArray("power_monster_types");
powerGrants.add(powerGrant);
}
return powerGrants;
}
// [{power_type: "X", power_actions: [{power_arguments: [a, b]}, ...]}, ...]
// -> power_type -> list of argument arrays (one per power_actions entry)
public static LinkedHashMap<String, ArrayList<int[]>> readPowerActions(JSONArray actionsJson) {
LinkedHashMap<String, ArrayList<int[]>> powerActions = new LinkedHashMap<>();
JSONObject entry;
JSONArray argumentsJson;
ArrayList<int[]> arguments;
for (int i = 0; i < actionsJson.length(); i++) {
entry = actionsJson.getJSONObject(i);
argumentsJson = entry.getJSONArray("power_actions");
arguments = new ArrayList<>();
for (int j = 0; j < argumentsJson.length(); j++)
arguments.add(readIntArray(argumentsJson.getJSONObject(j).getJSONArray("power_arguments")));
powerActions.put(entry.getString("power_type"), arguments);
}
return powerActions;
}
}
-180
View File
@@ -1,180 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.DeedTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads DEED templates from the client cache JSON (COBJECTS/DEED/<id>.json).
public class DeedParser {
private static final String deedPath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/DEED/";
public static HashMap<Integer, DeedTemplate> parseDeedFolder() {
HashMap<Integer, DeedTemplate> deedTemplates = new HashMap<>();
File[] deedFiles = CacheParserUtil.listJsonFiles(deedPath);
int templateId;
JSONObject deedJson;
int failedCount = 0;
for (File deedFile : deedFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(deedFile);
deedJson = CacheParserUtil.readJsonFile(deedFile);
deedTemplates.put(templateId, parseDeedEntry(templateId, deedJson));
} catch (Exception e) {
Logger.error(deedFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + deedTemplates.size() + " of " + deedFiles.length + " deed templates");
// Partial deed data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return deedTemplates;
}
private static DeedTemplate parseDeedEntry(int templateId, JSONObject deedJson) {
DeedTemplate deed = new DeedTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
String flag;
deed.template_id = templateId;
// obj_
deed.obj_name = deedJson.getString("obj_name");
deed.obj_pickable = deedJson.getBoolean("obj_pickable");
deed.obj_gravity = deedJson.getFloat("obj_gravity");
deed.obj_cull_distance = deedJson.getFloat("obj_cull_distance");
deed.obj_scale = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_scale"));
deed.obj_render_object = deedJson.getInt("obj_render_object");
deed.obj_double_fusion = deedJson.getBoolean("obj_double_fusion");
deed.obj_forward_vector = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_forward_vector"));
deed.obj_tracking_name = deedJson.getString("obj_tracking_name");
deed.obj_max_tracking_distance = deedJson.getFloat("obj_max_tracking_distance");
deed.obj_icon = deedJson.getInt("obj_icon");
deed.obj_gravity_f = deedJson.getFloat("obj_gravity_f");
deed.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(deedJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
deed.obj_arc_hardpoint_list = deedJson.getJSONArray("obj_arc_hardpoint_list");
deed.obj_sparse_data.putAll(CacheParserUtil.readSparseData(deedJson.getJSONObject("obj_sparse_data")));
deed.obj_render_object_low_detail = deedJson.getInt("obj_render_object_low_detail");
deed.obj_default_alignment = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_default_alignment"));
deed.obj_sound_table = CacheParserUtil.readLabel(deedJson, "obj_sound_table");
// combat_
deed.combat_health_current = deedJson.getFloat("combat_health_current");
deed.combat_health_full = deedJson.getFloat("combat_health_full");
deed.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, deedJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
deed.combat_powers = deedJson.getJSONArray("combat_powers");
// item_
deed.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, deedJson.getString("item_type"), templateId, "item_type");
deed.item_eq_slots_value = deedJson.getInt("item_eq_slots_value");
deed.item_eq_slots_type = deedJson.getBoolean("item_eq_slots_type");
deed.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
deed.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
deed.item_takeable = deedJson.getBoolean("item_takeable");
deed.item_value = deedJson.getInt("item_value");
deed.item_wt = deedJson.getInt("item_wt");
deed.item_passive_defense_mod = deedJson.getFloat("item_passive_defense_mod");
deed.item_base_name = deedJson.getString("item_base_name");
deed.item_dsc = deedJson.getString("item_dsc");
deed.item_render_object_female = deedJson.getInt("item_render_object_female");
deed.item_health_full = deedJson.getFloat("item_health_full");
deed.item_skill_used = CacheParserUtil.readLabel(deedJson, "item_skill_used");
deed.item_skill_mastery_used = CacheParserUtil.readLabel(deedJson, "item_skill_mastery_used");
deed.item_parry_anim_id = deedJson.getInt("item_parry_anim_id");
deed.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, deedJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// "None" is the client's empty marker, not a flag
itemFlags = deedJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
deed.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
deed.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, deedJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
deed.item_post_item_id = deedJson.getInt("item_post_item_id");
deed.item_initial_charges = deedJson.getInt("item_initial_charges");
deed.item_book_arcana = deedJson.getInt("item_book_arcana");
deed.item_skill_req.putAll(CacheParserUtil.readStringValueMap(deedJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = deedJson.getJSONObject("item_race_req");
deed.item_race_req_restrict = raceReq.getBoolean("restrict");
deed.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = deedJson.getJSONObject("item_class_req");
deed.item_class_req_restrict = classReq.getBoolean("restrict");
deed.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = deedJson.getJSONObject("item_disc_req");
deed.item_disc_req_restrict = discReq.getBoolean("restrict");
deed.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
deed.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, deedJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
deed.item_level_req = deedJson.getInt("item_level_req");
deed.item_rank_req = deedJson.getInt("item_rank_req");
deed.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, deedJson.getString("item_sex_req"), templateId, "item_sex_req");
deed.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(deedJson.getJSONArray("item_user_power_action")));
deed.item_power_grant.addAll(CacheParserUtil.readPowerGrants(deedJson.getJSONArray("item_power_grant"), templateId));
deed.item_power_action.putAll(CacheParserUtil.readPowerActions(deedJson.getJSONArray("item_power_action")));
deed.item_sheathable = deedJson.getBoolean("item_sheathable");
deed.item_has_stub = deedJson.getBoolean("item_has_stub");
deed.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, deedJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
deed.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, deedJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
deed.item_bane_rank = deedJson.getInt("item_bane_rank");
deed.item_ignore_saved_actions = deedJson.getBoolean("item_ignore_saved_actions");
// deed_
deed.deed_type = deedJson.getInt("deed_type");
deed.deed_furniture_id = deedJson.getInt("deed_furniture_id");
deed.deed_target_id = deedJson.getInt("deed_target_id");
deed.deed_employment = deedJson.getInt("deed_employment");
deed.deed_start_rank = deedJson.getInt("deed_start_rank");
deed.deed_name_lookup = deedJson.getInt("deed_name_lookup");
deed.deed_indoors = deedJson.getBoolean("deed_indoors");
deed.deed_is_fortress = deedJson.getBoolean("deed_is_fortress");
deed.deed_namelookup_val = deedJson.getFloat("deed_namelookup_val");
deed.deed_custom_city = deedJson.getBoolean("deed_custom_city");
deed.deed_structure_id = deedJson.getInt("deed_structure_id");
return deed;
}
}
-248
View File
@@ -1,248 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.ItemTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads ITEM templates from the client cache JSON (COBJECTS/ITEM/<id>.json).
public class ItemParser {
private static final String itemPath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/ITEM/";
public static HashMap<Integer, ItemTemplate> parseItemFolder() {
HashMap<Integer, ItemTemplate> itemTemplates = new HashMap<>();
File[] itemFiles = CacheParserUtil.listJsonFiles(itemPath);
int templateId;
JSONObject itemJson;
int failedCount = 0;
for (File itemFile : itemFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(itemFile);
itemJson = CacheParserUtil.readJsonFile(itemFile);
itemTemplates.put(templateId, parseItemEntry(templateId, itemJson));
} catch (Exception e) {
Logger.error(itemFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + itemTemplates.size() + " of " + itemFiles.length + " item templates");
// Partial item data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return itemTemplates;
}
private static ItemTemplate parseItemEntry(int templateId, JSONObject itemJson) {
ItemTemplate item = new ItemTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
JSONArray useFlags;
String flag;
Object stubJson;
JSONArray stubHolder;
item.template_id = templateId;
// obj_
item.obj_name = itemJson.getString("obj_name");
item.obj_pickable = itemJson.getBoolean("obj_pickable");
item.obj_gravity = itemJson.getFloat("obj_gravity");
item.obj_cull_distance = itemJson.getFloat("obj_cull_distance");
item.obj_scale = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_scale"));
item.obj_render_object = itemJson.getInt("obj_render_object");
item.obj_double_fusion = itemJson.getBoolean("obj_double_fusion");
item.obj_forward_vector = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_forward_vector"));
item.obj_tracking_name = itemJson.getString("obj_tracking_name");
item.obj_max_tracking_distance = itemJson.getFloat("obj_max_tracking_distance");
item.obj_icon = itemJson.getInt("obj_icon");
item.obj_gravity_f = itemJson.getFloat("obj_gravity_f");
item.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(itemJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
item.obj_arc_hardpoint_list = itemJson.getJSONArray("obj_arc_hardpoint_list");
item.obj_sparse_data.putAll(CacheParserUtil.readSparseData(itemJson.getJSONObject("obj_sparse_data")));
item.obj_render_object_low_detail = itemJson.getInt("obj_render_object_low_detail");
item.obj_default_alignment = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_default_alignment"));
item.obj_sound_table = CacheParserUtil.readHashLabel(itemJson, "obj_sound_table", templateId);
// combat_
item.combat_health_current = itemJson.getFloat("combat_health_current");
item.combat_health_full = itemJson.getFloat("combat_health_full");
item.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, itemJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
item.combat_powers = itemJson.getJSONArray("combat_powers");
// item_
item.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, itemJson.getString("item_type"), templateId, "item_type");
item.item_eq_slots_value = itemJson.getInt("item_eq_slots_value");
item.item_eq_slots_type = itemJson.getBoolean("item_eq_slots_type");
// The exporter writes ["NONE"] for an empty slot mask
item.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, itemJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
item.item_eq_slots_or.remove(mbEnums.EquipSlotType.NONE);
item.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, itemJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
item.item_eq_slots_and.remove(mbEnums.EquipSlotType.NONE);
item.item_takeable = itemJson.getBoolean("item_takeable");
item.item_value = itemJson.getInt("item_value");
item.item_wt = itemJson.getInt("item_wt");
item.item_passive_defense_mod = itemJson.getFloat("item_passive_defense_mod");
item.item_base_name = itemJson.getString("item_base_name");
item.item_dsc = itemJson.getString("item_dsc");
item.item_render_object_female = itemJson.getInt("item_render_object_female");
item.item_health_full = itemJson.getFloat("item_health_full");
item.item_skill_used = CacheParserUtil.readHashLabel(itemJson, "item_skill_used", templateId);
item.item_skill_mastery_used = CacheParserUtil.readHashLabel(itemJson, "item_skill_mastery_used", templateId);
item.item_parry_anim_id = itemJson.getInt("item_parry_anim_id");
item.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, itemJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// The client reads these blocks by item type, not by key presence
if (item.item_type == mbEnums.ItemType.WEAPON) {
item.item_weapon = parseWeapon(templateId, itemJson.getJSONObject("item_weapon"));
item.item_primary_attr = CacheParserUtil.readEnum(mbEnums.AttributeType.class, itemJson.getString("item_primary_attr"), templateId, "item_primary_attr");
item.item_secondary_attr = CacheParserUtil.readEnum(mbEnums.AttributeType.class, itemJson.getString("item_secondary_attr"), templateId, "item_secondary_attr");
}
if (item.item_type == mbEnums.ItemType.ARMOR) {
item.item_bulk_factor = itemJson.getFloat("item_bulk_factor");
item.item_defense_rating = itemJson.getInt("item_defense_rating");
}
// "None" is bit 0 in the client's flag tables, not a real flag
itemFlags = itemJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
item.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
useFlags = itemJson.getJSONArray("item_use_flags");
for (int i = 0; i < useFlags.length(); i++) {
flag = useFlags.getString(i);
if (flag.equals("NONE"))
continue;
item.item_use_flags.add(CacheParserUtil.readEnum(mbEnums.ItemUseFlags.class, flag, templateId, "item_use_flags"));
}
item.item_post_item_id = itemJson.getInt("item_post_item_id");
item.item_initial_charges = itemJson.getInt("item_initial_charges");
item.item_book_arcana = itemJson.getInt("item_book_arcana");
item.item_skill_req.putAll(CacheParserUtil.readStringValueMap(itemJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = itemJson.getJSONObject("item_race_req");
item.item_race_req_restrict = raceReq.getBoolean("restrict");
item.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = itemJson.getJSONObject("item_class_req");
item.item_class_req_restrict = classReq.getBoolean("restrict");
item.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = itemJson.getJSONObject("item_disc_req");
item.item_disc_req_restrict = discReq.getBoolean("restrict");
item.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
item.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, itemJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
item.item_level_req = itemJson.getInt("item_level_req");
item.item_rank_req = itemJson.getInt("item_rank_req");
item.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, itemJson.getString("item_sex_req"), templateId, "item_sex_req");
item.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(itemJson.getJSONArray("item_user_power_action")));
item.item_power_grant.addAll(CacheParserUtil.readPowerGrants(itemJson.getJSONArray("item_power_grant"), templateId));
item.item_power_action.putAll(CacheParserUtil.readPowerActions(itemJson.getJSONArray("item_power_action")));
item.item_sheathable = itemJson.getBoolean("item_sheathable");
if (item.item_sheathable) {
item.item_sheath_slot = CacheParserUtil.readEnum(mbEnums.SheathSlot.class, itemJson.getString("item_sheath_slot"), templateId, "item_sheath_slot");
item.item_sheath_offset = CacheParserUtil.readVector3(itemJson.getJSONArray("item_sheath_offset"));
item.item_sheath_rotation = CacheParserUtil.readVector3(itemJson.getJSONArray("item_sheath_rotation"));
}
stubJson = itemJson.get("item_has_stub");
if (stubJson instanceof JSONArray) {
stubHolder = (JSONArray) stubJson;
item.item_has_stub = true;
item.item_stub_flag = stubHolder.getBoolean(0);
item.item_stub_id = stubHolder.getInt(1);
} else {
item.item_has_stub = itemJson.getBoolean("item_has_stub");
}
item.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, itemJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
item.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, itemJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
item.item_bane_rank = itemJson.getInt("item_bane_rank");
item.item_ignore_saved_actions = itemJson.getBoolean("item_ignore_saved_actions");
return item;
}
private static ItemTemplate.Weapon parseWeapon(int templateId, JSONObject weaponJson) {
ItemTemplate.Weapon weapon = new ItemTemplate.Weapon();
JSONArray damageJson;
JSONObject damageEntry;
ItemTemplate.Damage damage;
weapon.weapon_wepspeed = weaponJson.getFloat("weapon_wepspeed");
weapon.weapon_max_range = weaponJson.getFloat("weapon_max_range");
weapon.weapon_projectile_id = weaponJson.getInt("weapon_projectile_id");
weapon.weapon_projectile_speed = weaponJson.getFloat("weapon_projectile_speed");
weapon.weapon_combat_idle_anim = weaponJson.getInt("weapon_combat_idle_anim");
damageJson = weaponJson.getJSONArray("weapon_damage");
for (int i = 0; i < damageJson.length(); i++) {
damageEntry = damageJson.getJSONObject(i);
damage = new ItemTemplate.Damage();
damage.damage_type = CacheParserUtil.readEnum(mbEnums.DamageType.class, damageEntry.getString("damage_type"), templateId, "weapon_damage");
damage.damage_min = damageEntry.getInt("damage_min");
damage.damage_max = damageEntry.getInt("damage_max");
weapon.weapon_damage.add(damage);
}
weapon.weapon_attack_anim_right.addAll(CacheParserUtil.readIntArrays(weaponJson.getJSONArray("weapon_attack_anim_right")));
weapon.weapon_attack_anim_left.addAll(CacheParserUtil.readIntArrays(weaponJson.getJSONArray("weapon_attack_anim_left")));
return weapon;
}
}
-118
View File
@@ -1,118 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.objects.AbstractGameObject;
import engine.objects.BaseClass;
import engine.parsers.objects.RuneTemplate;
import org.pmw.tinylog.Logger;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
// Dev-only check that compares DB-built objects with the cache JSON before a loader is switched.
// Enabled by the optional env var MB_PARITY_CHECK; not in the ConfigManager enum, which requires every entry.
public class ParityCheck {
private static final String FLAG_NAME = "MB_PARITY_CHECK";
private static final LinkedHashMap<String, Consumer<ParityCheck>> comparators = new LinkedHashMap<>();
private final String table;
private int comparedCount = 0;
private int differenceCount = 0;
private int missingCount = 0;
static {
register("static_rune_baseclass", ParityCheck::compareBaseClasses);
}
private ParityCheck(String table) {
this.table = table;
}
public static boolean isEnabled() {
return ConfigManager.configMap.getOrDefault(FLAG_NAME, "false").equalsIgnoreCase("true");
}
public static void register(String table, Consumer<ParityCheck> comparator) {
comparators.put(table, comparator);
}
public static void run() {
ParityCheck check;
for (String table : comparators.keySet()) {
check = new ParityCheck(table);
try {
comparators.get(table).accept(check);
} catch (Exception e) {
Logger.error("Parity: " + table + ": comparator failed: " + e);
}
Logger.info("Parity: " + table + ": " + check.comparedCount + " compared, "
+ check.differenceCount + " differences, " + check.missingCount + " missing in json");
}
}
// Returns false when the row has no JSON counterpart, so the comparator can skip its fields.
public boolean compareRow(int id, Object jsonObject) {
comparedCount++;
if (jsonObject == null) {
missingCount++;
Logger.warn("Parity: " + table + "/" + id + ": missing in json");
return false;
}
return true;
}
public void compareField(int id, String field, Object dbValue, Object jsonValue) {
if (Objects.equals(dbValue, jsonValue))
return;
differenceCount++;
Logger.warn("Parity: " + table + "/" + id + "/" + field + ": db=" + dbValue + " json=" + jsonValue);
}
private static void compareBaseClasses(ParityCheck check) {
ConcurrentHashMap<Integer, AbstractGameObject> cache;
BaseClass baseClass;
RuneTemplate rune;
cache = DbManager.getMap(mbEnums.GameObjectType.BaseClass);
if (cache == null) {
Logger.warn("Parity: static_rune_baseclass: no BaseClass objects loaded");
return;
}
for (AbstractGameObject cached : cache.values()) {
baseClass = (BaseClass) cached;
rune = RuneTemplate._runes.get(baseClass.getObjectUUID());
if (!check.compareRow(baseClass.getObjectUUID(), rune))
continue;
check.compareField(baseClass.getObjectUUID(), "name", baseClass.getName(), rune.obj_name);
}
}
}
-332
View File
@@ -1,332 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.RuneTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads RUNE templates from the client cache JSON (COBJECTS/RUNE/<id>.json).
public class RuneParser {
private static final String runePath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/RUNE/";
public static HashMap<Integer, RuneTemplate> parseRuneFolder() {
HashMap<Integer, RuneTemplate> runeTemplates = new HashMap<>();
File[] runeFiles = CacheParserUtil.listJsonFiles(runePath);
int templateId;
JSONObject runeJson;
int failedCount = 0;
for (File runeFile : runeFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(runeFile);
runeJson = CacheParserUtil.readJsonFile(runeFile);
runeTemplates.put(templateId, parseRuneEntry(templateId, runeJson));
} catch (Exception e) {
Logger.error(runeFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + runeTemplates.size() + " of " + runeFiles.length + " rune templates");
// Partial rune data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return runeTemplates;
}
private static RuneTemplate parseRuneEntry(int templateId, JSONObject runeJson) {
RuneTemplate rune = new RuneTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
String flag;
Object subType;
JSONObject speedJson;
JSONObject groupJson;
JSONArray skillGrantsJson;
JSONObject skillGrantJson;
RuneTemplate.SkillGrant skillGrant;
JSONArray skillAdjJson;
JSONObject skillAdjEntry;
JSONArray naturalAttacksJson;
JSONArray inventoryJson;
JSONObject inventoryEntryJson;
RuneTemplate.InventoryEntry inventoryEntry;
JSONArray bodyPartsJson;
JSONObject bodyPartJson;
RuneTemplate.BodyPart bodyPart;
rune.template_id = templateId;
// obj_
rune.obj_name = runeJson.getString("obj_name");
rune.obj_pickable = runeJson.getBoolean("obj_pickable");
rune.obj_gravity = runeJson.getFloat("obj_gravity");
rune.obj_cull_distance = runeJson.getFloat("obj_cull_distance");
rune.obj_scale = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_scale"));
rune.obj_render_object = runeJson.getInt("obj_render_object");
rune.obj_double_fusion = runeJson.getBoolean("obj_double_fusion");
rune.obj_forward_vector = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_forward_vector"));
rune.obj_tracking_name = runeJson.getString("obj_tracking_name");
rune.obj_max_tracking_distance = runeJson.getFloat("obj_max_tracking_distance");
rune.obj_icon = runeJson.getInt("obj_icon");
rune.obj_gravity_f = runeJson.getFloat("obj_gravity_f");
rune.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
rune.obj_arc_hardpoint_list = runeJson.getJSONArray("obj_arc_hardpoint_list");
rune.obj_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("obj_sparse_data")));
rune.obj_render_object_low_detail = runeJson.getInt("obj_render_object_low_detail");
rune.obj_default_alignment = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_default_alignment"));
rune.obj_sound_table = CacheParserUtil.readLabel(runeJson, "obj_sound_table");
// combat_
rune.combat_health_current = runeJson.getFloat("combat_health_current");
rune.combat_health_full = runeJson.getFloat("combat_health_full");
rune.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, runeJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
rune.combat_powers = runeJson.getJSONArray("combat_powers");
// item_
rune.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, runeJson.getString("item_type"), templateId, "item_type");
rune.item_eq_slots_value = runeJson.getInt("item_eq_slots_value");
rune.item_eq_slots_type = runeJson.getBoolean("item_eq_slots_type");
rune.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
rune.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
rune.item_takeable = runeJson.getBoolean("item_takeable");
rune.item_value = runeJson.getInt("item_value");
rune.item_wt = runeJson.getInt("item_wt");
rune.item_passive_defense_mod = runeJson.getFloat("item_passive_defense_mod");
rune.item_base_name = runeJson.getString("item_base_name");
rune.item_dsc = runeJson.getString("item_dsc");
rune.item_render_object_female = runeJson.getInt("item_render_object_female");
rune.item_health_full = runeJson.getFloat("item_health_full");
rune.item_skill_used = CacheParserUtil.readLabel(runeJson, "item_skill_used");
rune.item_skill_mastery_used = CacheParserUtil.readLabel(runeJson, "item_skill_mastery_used");
rune.item_parry_anim_id = runeJson.getInt("item_parry_anim_id");
rune.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// "None" is the client's empty marker, not a flag
itemFlags = runeJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
rune.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
rune.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, runeJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
rune.item_post_item_id = runeJson.getInt("item_post_item_id");
rune.item_initial_charges = runeJson.getInt("item_initial_charges");
rune.item_book_arcana = runeJson.getInt("item_book_arcana");
rune.item_skill_req.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = runeJson.getJSONObject("item_race_req");
rune.item_race_req_restrict = raceReq.getBoolean("restrict");
rune.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = runeJson.getJSONObject("item_class_req");
rune.item_class_req_restrict = classReq.getBoolean("restrict");
rune.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = runeJson.getJSONObject("item_disc_req");
rune.item_disc_req_restrict = discReq.getBoolean("restrict");
rune.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
rune.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
rune.item_level_req = runeJson.getInt("item_level_req");
rune.item_rank_req = runeJson.getInt("item_rank_req");
rune.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("item_sex_req"), templateId, "item_sex_req");
rune.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(runeJson.getJSONArray("item_user_power_action")));
rune.item_power_grant.addAll(CacheParserUtil.readPowerGrants(runeJson.getJSONArray("item_power_grant"), templateId));
rune.item_power_action.putAll(CacheParserUtil.readPowerActions(runeJson.getJSONArray("item_power_action")));
rune.item_sheathable = runeJson.getBoolean("item_sheathable");
rune.item_has_stub = runeJson.getBoolean("item_has_stub");
rune.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
rune.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, runeJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
rune.item_bane_rank = runeJson.getInt("item_bane_rank");
rune.item_ignore_saved_actions = runeJson.getBoolean("item_ignore_saved_actions");
// rune_
rune.rune_type = CacheParserUtil.readEnum(mbEnums.RuneType.class, runeJson.getString("rune_type"), templateId, "rune_type");
subType = runeJson.get("rune_sub_type");
if (subType instanceof String) {
rune.rune_sub_type = (String) subType;
} else {
rune.rune_sub_type = "";
rune.rune_sub_type_hash = (int) runeJson.getLong("rune_sub_type");
}
rune.rune_is_standard_character_creation = runeJson.getBoolean("rune_is_standard_character_creation");
rune.rune_creation_cost = runeJson.getInt("rune_creation_cost");
rune.rune_rank = runeJson.getInt("rune_rank");
rune.rune_pracs_per_level = runeJson.getInt("rune_pracs_per_level");
rune.rune_exp_req_to_level = runeJson.getFloat("rune_exp_req_to_level");
rune.rune_sex = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("rune_sex"), templateId, "rune_sex");
rune.rune_class_icon = runeJson.getInt("rune_class_icon");
rune.rune_health = runeJson.getInt("rune_health");
rune.rune_mana = runeJson.getInt("rune_mana");
rune.rune_stamina = runeJson.getInt("rune_stamina");
rune.rune_min_damage = runeJson.getFloat("rune_min_damage");
rune.rune_max_damage = runeJson.getFloat("rune_max_damage");
rune.rune_attack = runeJson.getInt("rune_attack");
rune.rune_defense = runeJson.getInt("rune_defense");
rune.rune_level = runeJson.getInt("rune_level");
speedJson = runeJson.getJSONObject("rune_speed");
for (String speedKey : speedJson.keySet())
rune.rune_speed.put(CacheParserUtil.readEnum(mbEnums.MovementType.class, speedKey, templateId, "rune_speed"), speedJson.getFloat(speedKey));
groupJson = runeJson.getJSONObject("rune_group");
rune.rune_group_type = groupJson.getInt("group_type");
rune.rune_group_is_faction = groupJson.getBoolean("group_is_faction");
rune.rune_group_is_guild = groupJson.getBoolean("group_is_guild");
rune.rune_dsc = runeJson.getString("rune_dsc");
rune.rune_fx_txt = runeJson.getString("rune_fx_txt");
rune.rune_group_tactics = runeJson.getLong("rune_group_tactics");
rune.rune_group_role_set = runeJson.getLong("rune_group_role_set");
rune.rune_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_enemy_monster_types"),
templateId, "rune_enemy_monster_types"));
rune.rune_groupee_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_groupee_monster_types")));
rune.rune_helper_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_helper_monster_types")));
rune.rune_not_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_not_enemy_monster_types"),
templateId, "rune_not_enemy_monster_types"));
rune.rune_enemy_gender = runeJson.getJSONArray("rune_enemy_gender");
skillGrantsJson = runeJson.getJSONArray("rune_skill_grant");
for (int i = 0; i < skillGrantsJson.length(); i++) {
skillGrantJson = skillGrantsJson.getJSONObject(i);
skillGrant = new RuneTemplate.SkillGrant();
skillGrant.skill_type = skillGrantJson.getString("skill_type");
skillGrant.skill_value = skillGrantJson.getInt("skill_value");
skillGrant.skill_granted_attrs.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_attrs"),
templateId, "skill_granted_attrs"));
skillGrant.skill_granted_skills.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_skills"),
templateId, "skill_granted_skills"));
skillGrant.skill_granted_powers.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_powers"),
templateId, "skill_granted_powers"));
skillGrant.skill_monster_types = skillGrantJson.getJSONArray("skill_monster_types");
rune.rune_skill_grant.add(skillGrant);
}
skillAdjJson = runeJson.getJSONArray("rune_skill_adj");
for (int i = 0; i < skillAdjJson.length(); i++) {
skillAdjEntry = skillAdjJson.getJSONObject(i);
rune.rune_skill_adj.put(skillAdjEntry.getString("skill_type"),
CacheParserUtil.readIntArrays(skillAdjEntry.getJSONArray("skill_adjusts")));
}
rune.rune_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_attr_adj"),
"attr_type", "attr_value", templateId, "rune_attr_adj"));
rune.rune_max_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_max_attr_adj"),
"attr_type", "attr_value", templateId, "rune_max_attr_adj"));
naturalAttacksJson = runeJson.getJSONArray("rune_naturalattacks");
for (int i = 0; i < naturalAttacksJson.length(); i++)
rune.rune_naturalattacks.add(CacheParserUtil.readFloatArray(naturalAttacksJson.getJSONArray(i)));
rune.rune_enchantment_type = runeJson.getJSONArray("rune_enchantment_type");
inventoryJson = runeJson.getJSONArray("rune_inventory_contents");
for (int i = 0; i < inventoryJson.length(); i++) {
inventoryEntryJson = inventoryJson.getJSONObject(i);
inventoryEntry = new RuneTemplate.InventoryEntry();
inventoryEntry.type = inventoryEntryJson.getString("type");
if (inventoryEntryJson.has("gold"))
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("gold"));
else if (inventoryEntryJson.has("table"))
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("table"));
else
Logger.error(templateId + ": rune_inventory_contents entry of type " + inventoryEntry.type + " has no values");
rune.rune_inventory_contents.add(inventoryEntry);
}
rune.rune_renderable = runeJson.getBoolean("rune_renderable");
// Render fields are absent on some runes
if (runeJson.has("rune_scale_factor"))
rune.rune_scale_factor = CacheParserUtil.readVector3(runeJson.getJSONArray("rune_scale_factor"));
if (runeJson.has("rune_skeleton"))
rune.rune_skeleton = runeJson.getInt("rune_skeleton");
if (runeJson.has("rune_slope_hugger"))
rune.rune_slope_hugger = runeJson.getBoolean("rune_slope_hugger");
if (runeJson.has("rune_can_fly"))
rune.rune_can_fly = runeJson.getBoolean("rune_can_fly");
if (runeJson.has("rune_death_effect"))
rune.rune_death_effect = runeJson.getInt("rune_death_effect");
if (runeJson.has("rune_tombstone_id"))
rune.rune_tombstone_id = runeJson.getInt("rune_tombstone_id");
if (runeJson.has("rune_body_parts")) {
bodyPartsJson = runeJson.getJSONArray("rune_body_parts");
for (int i = 0; i < bodyPartsJson.length(); i++) {
bodyPartJson = bodyPartsJson.getJSONObject(i);
bodyPart = new RuneTemplate.BodyPart();
bodyPart.body_part_render = bodyPartJson.getInt("body_part_render");
bodyPart.body_part_position = CacheParserUtil.readVector3(bodyPartJson.getJSONArray("body_part_position"));
rune.rune_body_parts.add(bodyPart);
}
}
if (runeJson.has("rune_hair"))
rune.rune_hair.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_hair")));
if (runeJson.has("rune_beard"))
rune.rune_beard.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_beard")));
rune.rune_natural_power_attack = runeJson.getInt("rune_natural_power_attack");
rune.rune_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("rune_sparse_data")));
return rune;
}
}
@@ -1,15 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
// Common type for ITEM, DEED and RUNE cache templates, which share one id space.
// Shared obj_/combat_/item_ getters arrive with JSON-Refactor-Plan Task 1.1.
public interface CObjectTemplate {
}
@@ -1,122 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Deed template as defined by the client's cache data
// (MBDATA/data/COBJECTS/DEED). Field names match the JSON keys.
//
// Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String.
//
// Read only once loaded.
public class DeedTemplate implements CObjectTemplate {
public static HashMap<Integer, DeedTemplate> _deeds = new HashMap<>();
public int template_id;
// obj_
public String obj_name;
public boolean obj_pickable;
public float obj_gravity;
public float obj_cull_distance;
public Vector3fImmutable obj_scale;
public int obj_render_object;
public boolean obj_double_fusion;
public Vector3fImmutable obj_forward_vector;
public String obj_tracking_name;
public float obj_max_tracking_distance;
public int obj_icon;
public float obj_gravity_f;
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
public JSONArray obj_arc_hardpoint_list;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public int obj_render_object_low_detail;
public Vector3fImmutable obj_default_alignment;
public String obj_sound_table;
// combat_
public float combat_health_current;
public float combat_health_full;
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
public JSONArray combat_powers;
// item_
public mbEnums.ItemType item_type;
public int item_eq_slots_value;
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used;
public String item_skill_mastery_used;
public int item_parry_anim_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_post_item_id;
public int item_initial_charges;
public int item_book_arcana;
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
public boolean item_race_req_restrict;
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public boolean item_class_req_restrict;
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public boolean item_disc_req_restrict;
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
public int item_level_req;
public int item_rank_req;
public mbEnums.SexType item_sex_req;
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
public boolean item_sheathable;
public boolean item_has_stub;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
public int item_bane_rank;
public boolean item_ignore_saved_actions;
// deed_
public int deed_type;
public int deed_furniture_id;
public int deed_target_id;
public int deed_employment;
public int deed_start_rank;
public int deed_name_lookup;
public boolean deed_indoors;
public boolean deed_is_fortress;
public float deed_namelookup_val;
public boolean deed_custom_city;
public int deed_structure_id;
}
@@ -1,165 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Item template as defined by the client's cache data
// (MBDATA/data/COBJECTS/ITEM). Field names match the JSON keys.
// Field layout follows ArcItemTemplate_Load (sb.exe 0x4AABD0).
//
// Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String.
//
// Not to be confused with engine.objects.ItemTemplate, which is loaded
// from the database. Read only once loaded.
public class ItemTemplate implements CObjectTemplate {
public static HashMap<Integer, ItemTemplate> _items = new HashMap<>();
public int template_id;
// obj_
public String obj_name;
public boolean obj_pickable;
public float obj_gravity;
public float obj_cull_distance;
public Vector3fImmutable obj_scale;
public int obj_render_object;
public boolean obj_double_fusion;
public Vector3fImmutable obj_forward_vector;
public String obj_tracking_name;
public float obj_max_tracking_distance;
public int obj_icon;
public float obj_gravity_f;
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
public JSONArray obj_arc_hardpoint_list;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public int obj_render_object_low_detail;
public Vector3fImmutable obj_default_alignment;
public String obj_sound_table;
// combat_
public float combat_health_current;
public float combat_health_full;
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
public JSONArray combat_powers;
// item_
public mbEnums.ItemType item_type;
// Index into the client's equip slot names; the leading slot of EQSLOTAND.
public int item_eq_slots_value;
// true: any one of item_eq_slots_or; false: all of item_eq_slots_and (sb.exe 0x4AC430).
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used;
public String item_skill_mastery_used;
public int item_parry_anim_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
// WEAPON only; null otherwise.
public Weapon item_weapon;
public mbEnums.AttributeType item_primary_attr;
public mbEnums.AttributeType item_secondary_attr;
// ARMOR only.
public float item_bulk_factor;
public int item_defense_rating;
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_post_item_id;
public int item_initial_charges;
// Read as an unsigned value, possibly a hash; 0 in every ITEM file.
public int item_book_arcana;
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
// For race, class and disc: restrict true means the list is excluded
// (RACERESTRICT), false means it is required (RACEREQ).
public boolean item_race_req_restrict;
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public boolean item_class_req_restrict;
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public boolean item_disc_req_restrict;
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
public int item_level_req;
public int item_rank_req;
public mbEnums.SexType item_sex_req;
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
// Sheath fields are only present when item_sheathable is true.
public boolean item_sheathable;
public mbEnums.SheathSlot item_sheath_slot;
public Vector3fImmutable item_sheath_offset;
public Vector3fImmutable item_sheath_rotation;
// The JSON holds false, or [flag, id] when the stub is present (ArcItemStubHolder2, sb.exe 0x4A8D40).
// Meaning of the flag is not known.
public boolean item_has_stub;
public boolean item_stub_flag;
public int item_stub_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
public int item_bane_rank;
public boolean item_ignore_saved_actions;
// item_weapon (ArcAttackInfo_LoadWeapon, sb.exe 0x61FA60).
public static class Weapon {
public float weapon_wepspeed;
public float weapon_max_range;
public int weapon_projectile_id;
public float weapon_projectile_speed;
public int weapon_combat_idle_anim;
public final ArrayList<Damage> weapon_damage = new ArrayList<>();
// [animation, duration] pairs in file order; names from engine.objects.ItemTemplate, not the client.
public final ArrayList<int[]> weapon_attack_anim_right = new ArrayList<>();
public final ArrayList<int[]> weapon_attack_anim_left = new ArrayList<>();
}
// One entry of weapon_damage.
public static class Damage {
public mbEnums.DamageType damage_type;
public int damage_min;
public int damage_max;
}
}
@@ -1,27 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import org.json.JSONArray;
import java.util.LinkedHashMap;
// One entry of item_power_grant.
// The granted lists are [key, value] pairs, unsigned key and signed value (sb.exe 0x6D7640).
// power_monster_types is empty in every DEED and RUNE file, so it is kept as raw JSON.
public class PowerGrant {
public String power_type;
public int power_value;
public final LinkedHashMap<String, Integer> power_granted_attrs = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> power_granted_skills = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> power_granted_powers = new LinkedHashMap<>();
public JSONArray power_monster_types;
}
@@ -1,225 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Rune template as defined by the client's cache data
// (MBDATA/data/COBJECTS/RUNE). Field names match the JSON keys.
//
// Covers every rune the client knows about: player races, classes,
// disciplines, talents and mob/pet/npc runes alike.
//
// Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String.
//
// Read only once loaded.
public class RuneTemplate implements CObjectTemplate {
public static HashMap<Integer, RuneTemplate> _runes = new HashMap<>();
public int template_id;
// obj_
public String obj_name;
public boolean obj_pickable;
public float obj_gravity;
public float obj_cull_distance;
public Vector3fImmutable obj_scale;
public int obj_render_object;
public boolean obj_double_fusion;
public Vector3fImmutable obj_forward_vector;
public String obj_tracking_name;
public float obj_max_tracking_distance;
public int obj_icon;
public float obj_gravity_f;
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
public JSONArray obj_arc_hardpoint_list;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public int obj_render_object_low_detail;
public Vector3fImmutable obj_default_alignment;
public String obj_sound_table;
// combat_
public float combat_health_current;
public float combat_health_full;
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
public JSONArray combat_powers;
// item_
public mbEnums.ItemType item_type;
public int item_eq_slots_value;
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used;
public String item_skill_mastery_used;
public int item_parry_anim_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_post_item_id;
public int item_initial_charges;
public int item_book_arcana;
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
public boolean item_race_req_restrict;
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public boolean item_class_req_restrict;
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public boolean item_disc_req_restrict;
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
public int item_level_req;
public int item_rank_req;
public mbEnums.SexType item_sex_req;
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
public boolean item_sheathable;
public boolean item_has_stub;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
public int item_bane_rank;
public boolean item_ignore_saved_actions;
// rune_
public mbEnums.RuneType rune_type;
public String rune_sub_type;
// A handful of runes carry a number instead of a name in rune_sub_type.
// These look like string hashes the cache converter could not resolve.
// Stored here (wrapped to int) with rune_sub_type left empty.
public int rune_sub_type_hash;
public boolean rune_is_standard_character_creation;
public int rune_creation_cost;
public int rune_rank;
public int rune_pracs_per_level;
public float rune_exp_req_to_level;
public mbEnums.SexType rune_sex;
public int rune_class_icon;
public int rune_health;
public int rune_mana;
public int rune_stamina;
public float rune_min_damage;
public float rune_max_damage;
public int rune_attack;
public int rune_defense;
public int rune_level;
public final EnumMap<mbEnums.MovementType, Float> rune_speed = new EnumMap<>(mbEnums.MovementType.class);
public int rune_group_type;
public boolean rune_group_is_faction;
public boolean rune_group_is_guild;
public String rune_dsc;
public String rune_fx_txt;
// Bit vectors. The existing ItemTemplate decodes these as MobBehaviourType.
public long rune_group_tactics;
public long rune_group_role_set;
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
public final ArrayList<Integer> rune_groupee_monster_types = new ArrayList<>();
public final ArrayList<Integer> rune_helper_monster_types = new ArrayList<>();
public final EnumSet<mbEnums.MonsterType> rune_not_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
// Empty in every RUNE file; shape unknown.
public JSONArray rune_enemy_gender;
public final ArrayList<SkillGrant> rune_skill_grant = new ArrayList<>();
// skill_type -> list of [value, value] pairs, in file order.
public final LinkedHashMap<String, ArrayList<int[]>> rune_skill_adj = new LinkedHashMap<>();
public final EnumMap<mbEnums.AttributeType, Integer> rune_attr_adj = new EnumMap<>(mbEnums.AttributeType.class);
public final EnumMap<mbEnums.AttributeType, Integer> rune_max_attr_adj = new EnumMap<>(mbEnums.AttributeType.class);
// One float[10] per natural attack. Same "attack info" struct as a weapon
// (see MBDATA/docs/client-map.md): [0] attack speed, [1] range, [2] unknown,
// [3] equip slot (1 = RHELD, 2 = LHELD), [4] projectile template id,
// [5] projectile speed, [6] swing anim, [7] damage type, [8] min damage,
// [9] max damage
public final ArrayList<float[]> rune_naturalattacks = new ArrayList<>();
// Empty in every RUNE file; shape unknown.
public JSONArray rune_enchantment_type;
public final ArrayList<InventoryEntry> rune_inventory_contents = new ArrayList<>();
public boolean rune_renderable;
// The following fields are only present when the rune is renderable
// (missing from 315 of 2695 files). They keep their defaults otherwise.
public Vector3fImmutable rune_scale_factor;
public int rune_skeleton;
public boolean rune_slope_hugger;
public boolean rune_can_fly;
public int rune_death_effect;
public int rune_tombstone_id;
public final ArrayList<BodyPart> rune_body_parts = new ArrayList<>();
public final ArrayList<Integer> rune_hair = new ArrayList<>();
public final ArrayList<Integer> rune_beard = new ArrayList<>();
public int rune_natural_power_attack;
public final HashMap<String, String> rune_sparse_data = new HashMap<>();
// One entry of rune_skill_grant; same layout as PowerGrant.
// skill_monster_types is empty in every RUNE file, so it is kept as raw JSON.
public static class SkillGrant {
public String skill_type;
public int skill_value;
public final LinkedHashMap<String, Integer> skill_granted_attrs = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> skill_granted_skills = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> skill_granted_powers = new LinkedHashMap<>();
public JSONArray skill_monster_types;
}
// One entry of rune_body_parts.
public static class BodyPart {
public int body_part_render;
public Vector3fImmutable body_part_position;
}
// One entry of rune_inventory_contents.
// type is GOLD or BOOTYTABLE.
// GOLD carries "gold": three floats (meaning not yet confirmed).
// BOOTYTABLE carries "table": two numbers (meaning not yet confirmed).
public static class InventoryEntry {
public String type;
public float[] values;
}
}
@@ -1,24 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
// One entry of item_user_power_action.
// The same power can appear more than once on a single template,
// so these are kept in a list rather than a map keyed by power.
public class UserPowerAction {
public String power;
public int[] arguments;
public UserPowerAction(String power, int[] arguments) {
this.power = power;
this.arguments = arguments;
}
}
-11
View File
@@ -1,11 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
// Parsers for the client's .wpak configuration files (powers, effects, etc).
package engine.parsers.wpak;
@@ -246,9 +246,7 @@ public class ApplyEffectPowerAction extends AbstractPowerAction {
if (this.effectID.equals("TAUNT")) { if (this.effectID.equals("TAUNT")) {
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth if (awo != null && awo.getObjectType() == GameObjectType.Mob) {
if (awo != null && awo.getObjectType() == GameObjectType.Mob && !((Mob) awo).isSiege()) {
((Mob) awo).setCombatTarget(source); ((Mob) awo).setCombatTarget(source);
ChatSystemMsg msg = ChatManager.CombatInfo(source, awo); ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
DispatchManager.sendToAllInRange(source, msg); DispatchManager.sendToAllInRange(source, msg);
-34
View File
@@ -21,12 +21,6 @@ import engine.net.client.ClientConnection;
import engine.net.client.ClientConnectionManager; import engine.net.client.ClientConnectionManager;
import engine.net.client.msg.login.*; import engine.net.client.msg.login.*;
import engine.objects.*; import engine.objects.*;
import engine.parsers.cache.CObjectIndex;
import engine.parsers.cache.DeedParser;
import engine.parsers.cache.ItemParser;
import engine.parsers.cache.RuneParser;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.server.world.WorldServer; import engine.server.world.WorldServer;
import engine.session.Session; import engine.session.Session;
@@ -290,34 +284,6 @@ public class LoginServer {
Logger.info("Loading Item Templates"); Logger.info("Loading Item Templates");
DbManager.ItemQueries.LOAD_ITEM_TEMPLATES(); DbManager.ItemQueries.LOAD_ITEM_TEMPLATES();
Logger.info("Loading Deed Templates");
DeedTemplate._deeds = DeedParser.parseDeedFolder();
if (DeedTemplate._deeds == null || DeedTemplate._deeds.isEmpty()) {
Logger.error("ABORT! Deed templates failed to load!");
exit(1);
}
Logger.info("Loading Rune Templates");
RuneTemplate._runes = RuneParser.parseRuneFolder();
if (RuneTemplate._runes == null || RuneTemplate._runes.isEmpty()) {
Logger.error("ABORT! Rune templates failed to load!");
exit(1);
}
// Qualified: engine.objects.ItemTemplate is the database-loaded class
Logger.info("Loading Cache Item Templates");
engine.parsers.objects.ItemTemplate._items = ItemParser.parseItemFolder();
if (engine.parsers.objects.ItemTemplate._items == null || engine.parsers.objects.ItemTemplate._items.isEmpty()) {
Logger.error("ABORT! Cache item templates failed to load!");
exit(1);
}
Logger.info("Indexing Cache Templates");
CObjectIndex.build();
Logger.info("Initializing Network"); Logger.info("Initializing Network");
Network.init(); Network.init();
-40
View File
@@ -31,13 +31,6 @@ import engine.net.client.ClientConnectionManager;
import engine.net.client.msg.UpdateStateMsg; import engine.net.client.msg.UpdateStateMsg;
import engine.net.client.msg.chat.ChatSystemMsg; import engine.net.client.msg.chat.ChatSystemMsg;
import engine.objects.*; import engine.objects.*;
import engine.parsers.cache.CObjectIndex;
import engine.parsers.cache.DeedParser;
import engine.parsers.cache.ItemParser;
import engine.parsers.cache.ParityCheck;
import engine.parsers.cache.RuneParser;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.util.MapLoader; import engine.util.MapLoader;
import engine.util.ThreadUtils; import engine.util.ThreadUtils;
@@ -310,34 +303,6 @@ public class WorldServer {
Logger.info("Back fill template modTables"); Logger.info("Back fill template modTables");
DbManager.ItemQueries.LOAD_TEMPLATE_MODTABLES(); DbManager.ItemQueries.LOAD_TEMPLATE_MODTABLES();
Logger.info("Loading Deed Templates");
DeedTemplate._deeds = DeedParser.parseDeedFolder();
if (DeedTemplate._deeds == null || DeedTemplate._deeds.isEmpty()) {
Logger.error("ABORT! Deed templates failed to load!");
exit(1);
}
Logger.info("Loading Rune Templates");
RuneTemplate._runes = RuneParser.parseRuneFolder();
if (RuneTemplate._runes == null || RuneTemplate._runes.isEmpty()) {
Logger.error("ABORT! Rune templates failed to load!");
exit(1);
}
// Qualified: engine.objects.ItemTemplate is the database-loaded class
Logger.info("Loading Cache Item Templates");
engine.parsers.objects.ItemTemplate._items = ItemParser.parseItemFolder();
if (engine.parsers.objects.ItemTemplate._items == null || engine.parsers.objects.ItemTemplate._items.isEmpty()) {
Logger.error("ABORT! Cache item templates failed to load!");
exit(1);
}
Logger.info("Indexing Cache Templates");
CObjectIndex.build();
Logger.info("Loading PromotionClasses"); Logger.info("Loading PromotionClasses");
DbManager.PromotionQueries.GET_ALL_PROMOTIONS(); DbManager.PromotionQueries.GET_ALL_PROMOTIONS();
@@ -396,11 +361,6 @@ public class WorldServer {
mbEnums.RaceType.initRaceTypeTables(); mbEnums.RaceType.initRaceTypeTables();
Race.loadAllRaces(); Race.loadAllRaces();
if (ParityCheck.isEnabled()) {
Logger.info("Running DB/JSON parity check");
ParityCheck.run();
}
Logger.info("Loading building slot/stuck location data."); Logger.info("Loading building slot/stuck location data.");
BuildingLocation.loadBuildingLocations(); BuildingLocation.loadBuildingLocations();
+57
View File
@@ -57,6 +57,63 @@ public class StringUtils {
return Long.toHexString(l).toUpperCase(); return Long.toHexString(l).toUpperCase();
} }
// Well done IDA Pro.
public static int hashString(String toHash) {
byte[] hashArray = toHash.getBytes();
int hash = 0;
int shift = 0;
if (hashArray.length == 8 || hashArray.length == 7) {
int ecx = 0;
if (hashArray.length == 8) {
ecx = hashArray[7];
}
int eax = hashArray[4];
int esi = ecx * 0x8;
eax ^= esi;
ecx ^= 0x5A0;
esi = hashArray[5];
eax <<= 4;
eax ^= esi;
esi = hashArray[6];
eax <<= 4;
eax ^= esi;
esi = hashArray[2];
eax <<= 5;
eax ^= esi;
esi = hashArray[1];
int edx = hashArray[0];
eax <<= 5;
eax ^= esi;
ecx /= 2;
ecx /= 2;
eax <<= 5;
ecx ^= edx;
eax ^= ecx;
return eax;
} else {
for (int i = 0; i < hashArray.length; i++) {
if (i == 0)
shift = 0;
else
shift += 5;
int toShift = hashArray[i] - 0x20;
int shifted = (toShift << shift);
hash ^= shifted;
if (shift > 24) {
int newShift = 0x20 - shift;
int newShifted = toShift >> newShift;
hash ^= newShifted;
if (shift > 27) {
shift -= 0x20;
}
}
}
return hash;
}
}
public static String wordWrap(String text, int LineWidth) { public static String wordWrap(String text, int LineWidth) {
StringTokenizer st = new StringTokenizer(text); StringTokenizer st = new StringTokenizer(text);
int SpaceLeft = LineWidth; int SpaceLeft = LineWidth;
+1 -2
View File
@@ -18,7 +18,6 @@ import engine.net.MessageDispatcher;
import engine.net.client.msg.chat.ChatSystemMsg; import engine.net.client.msg.chat.ChatSystemMsg;
import engine.objects.*; import engine.objects.*;
import engine.server.world.WorldServer; import engine.server.world.WorldServer;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -292,7 +291,7 @@ public class HourlyJobThread implements Runnable {
public static void processBanes() { public static void processBanes() {
//handle banes //handle banes
for (Bane bane : Bane.banes.values()) { for (Bane bane : Bane.banes.values()) {
if (bane.getLiveDate() != null && DateTime.now().isAfter(bane.getLiveDate().minusMinutes(1)) && bane.isStarted == false) if (bane.getLiveDate() != null && LocalDateTime.now().isAfter(bane.getLiveDate().minusMinutes(1)) && bane.isStarted == false)
bane.startBane(); bane.startBane();
} }
} }