Compare commits

..
Author SHA1 Message Date
MagicBotandClaude Opus 5.5 b56d4ee243 Note lossy power action maps in ItemTemplate
item_user_power_action and item_power_action are keyed by power, so
rune step tables (repeated powers, multiple power_actions entries) are
collapsed to one entry. Nothing reads the rune data today; the new
RuneTemplate keeps the full lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:21:41 -04:00
MagicBotandClaude Opus 5.5 0fb1c078ad Normalize case and punctuation in DamageType.getDamageType
The validity check compared names case-sensitively, so mixed-case
input such as "Fire" or "Crushing" from static_power_failcondition and
static_power_poweraction, and SourceType names in the fortitude check,
all resolved to NONE. Input is now stripped of spaces, commas and
hyphens and upper-cased before lookup, so "Anti-Siege" maps to
ANTISIEGE as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:18:38 -04:00
MagicBotandClaude Opus 5.5 c14231afb9 Add RuneType enum and DamageType.ANTISIEGE
RuneType covers the rune_type values in the client cache data
(CLASS, DISCIPLINE, RACE, TALENT). ANTISIEGE matches the
combat_attack_resist key in the cache data.

RuneTemplate.rune_type and combat_attack_resist on both cache
templates now use these enums.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:16:05 -04:00
MagicBotandClaude Opus 5.5 0d48080e38 Add cache parsers and POJOs for DEED and RUNE templates
New engine.parsers package:
- cache: DeedParser and RuneParser load COBJECTS/DEED and COBJECTS/RUNE
  JSON straight from the data dir, keyed by file id. CacheParserUtil
  holds the shared JSON helpers.
- objects: standalone DeedTemplate and RuneTemplate covering every
  field in the client data, plus shared PowerGrant/UserPowerAction.
- wpak: placeholder for upcoming wpak parsers.

Not wired into server startup yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:09:41 -04:00
MagicBot 777f7ad89d Merge branch 'subdate3' into magicbox-1.5.2.1 2026-10-15 09:58:34 -04:00
MagicBotandClaude Opus 5.5 40799f96f6 Mark summoned pets with MASK_PET
Summoned pets (not necro pets) now carry MASK_PET, so pet-targeted
AOE powers can find them. The checkForAggro pet scan is held off with
PET_AGGRO_ENABLED until it checks rune enemy lists, filters targets
and keeps pet targets (plan-mask-pet.md steps 2-4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:19:36 -04:00
MagicBotandClaude Opus 5.5 5262a602da Keep siege engine target when a new player loads it
InterestManager cleared the combat target of every live mob loaded for
a new player, so a siege engine firing at a wall dropped its target
whenever someone entered load range. Siege engines now keep it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:05:24 -04:00
MagicBotandClaude Opus 5.5 5b80614376 Restore regular mob MASK_PET aggro scan
The scan was removed as dead code, but creature runes list Pet in
rune_enemy_monster_types, so monsters are meant to aggro summoned pets.
Restored unchanged; it stays inactive until MASK_PET is set from the
rune data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 09:00:27 -04:00
MagicBotandClaude Opus 5.5 b60f2754f1 Remove siege and pet auto-aggro scans
Guard MASK_SIEGE and regular mob MASK_PET scans were dead code: no mob
ever carries either mask. Removed so they cannot start auto-aggroing
siege engines if the masks are wired up later. Safehold guards no
longer aggro siege engines. Guards still respond to a siege engine
damaging a city building.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:53:57 -04:00
MagicBotandClaude Opus 5.5 89cdf5655d Flag players hostile for damaging siege engines on a city grid
Damaging a siege engine on a city grid adds the attacker (or the owning
player, for pets) to that city's outlaw list, unless they are in the
city's nation or an allied nation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:53:24 -04:00
MagicBotandClaude Opus 5.5 c9208000bb Pull siege engine aggro on damage only
A siege engine switches to whoever damages it, player or pet, even when
it already has a target. Pre-roll melee retaliation, harmful-power
direct aggro (which also fires on misses) and Taunt no longer change a
siege engine's target. The attack timer is untouched, so a switched
engine still waits for its next cycle before firing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:52:56 -04:00
MagicBotandClaude Opus 5.5 531a2d028b Drop siege engine targets only on death or attack range
Siege engines dropped player targets at 100 units, at the 200 unit
drop-aggro range, or when the player was missing from playerAgroMap,
all well inside their 350 attack range. They now hold a character
target until it dies, can't be seen, or leaves getRange().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:52:09 -04:00
MagicBotandClaude Opus 5.5 ca33c5a14c Keep siege engines alive and targeting when released
Owner death, logout or invisibility dismissed a commanded siege engine
by clearing its target and marking it dead. Released engines now revert
to MOBILE in place, stay alive and keep their current target so any
nation member can re-command them with a Rod of Command. Commanding an
engine no longer clears the target of the old or new engine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 08:51:38 -04:00
MagicBot 5868530414 mobai aligned with rune data and client. 2026-10-15 07:31:30 -04:00
MagicBotandClaude Opus 5.5 66a731005e Use race rune natural attack damage for unarmed players
Unarmed base damage was a hardcoded 1-3 (TODO). Read it from the race
rune natural attack for the swinging hand (1-4 for all races), then
scale it with the existing formula, matching the client (0x46D200).
Falls back to 1-3 if the rune has no natural attack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:24:33 -04:00
MagicBotandClaude Opus 5.5 330db50985 Use rune ATTACK for regular mob ATR
The client (0x466570) gives mobiles their ATR straight from the rune
ATTACK. Read rune_attack from the rune template, falling back to the
mobbase atr when the rune has none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:15:26 -04:00
MagicBotandClaude Opus 5.5 12c08c940a Use rune MINDAMAGE/MAXDAMAGE for regular mob damage
The client (ArcMobile, 0x46D200) takes mob damage straight from the rune
MINDAMAGE/MAXDAMAGE with no stat scaling; natural attack damage is only
scaled for non-mobile characters. Read rune_min_damage/rune_max_damage
from the rune template, falling back to the mobbase row when the rune
has none. 2689 of 2770 mobbase rows already match the rune.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:13:52 -04:00
MagicBotandClaude Opus 5.5 b21ccdbb4c Use rune natural attack range for unarmed mobs
Unarmed mobs now add their rune natural attack range in processAttack
and use it in getRange. Most melee runes have 0, so they are unchanged;
ranged plant mobs (Gasser, Spitter, Needler, ...) get 150, Shuggra 50.
Natural range is not subject to the unverified 80 clamp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:05:25 -04:00
MagicBotandClaude Opus 5.5 a708a1f0b2 Use rune natural attack range for siege engines
Siege engine range was hardcoded to 300. Read it from the rune natural
attack instead (350 for Trebuchet, Mangonel and Ballista), matching the
client's RecalcMinWeaponRange. Falls back to 300 if the rune has none.
getNaturalAttack now returns the whole entry so speed and range share
the lookup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:04:11 -04:00
MagicBotandClaude Opus 5.5 b525a7a3ab Document verified rune natural attack layout
Field layout of rune_naturalattacks confirmed in the client (IDA): it is
the same attack info struct as a weapon, so [0] is WEPSPEED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:01:57 -04:00
MagicBotandClaude Opus 5.5 b690195233 Use race rune natural attack speed for unarmed players
Extend getNaturalAttackSpeed to resolve the rune from the attacker type:
mobbase rune for mobs, race rune for players. Unarmed players now swing
at their race rune speed (20 = 2.0 s per hand) instead of a flat 5000 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:56:21 -04:00
MagicBotandClaude Opus 5.5 9aa44d4e9a Use rune natural attack speed for unarmed mobs
Unarmed mobs used a flat 5000 ms swing delay. Parse rune_naturalattacks
from the rune template and use its speed (x100 ms) instead, with the
AttackDelay modifier and 1.0 s floor applied as for weapons. Siege
engines now fire every 17.5 s (speed 175); most creatures swing every 3.5 s.
Falls back to 5000 ms when the rune has no natural attack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 06:53:50 -04:00
MagicBot ab1c3f12d3 mobai null resolution 2026-10-14 15:22:38 -04:00
MagicBot 6017641896 mobai null resolution 2026-10-14 14:36:07 -04:00
Bardiel 9866632ad5 Mandrake resource hash updated 2026-07-17 22:28:37 +00:00
47 changed files with 1766 additions and 282 deletions

No files matched your search

@@ -468,7 +468,12 @@ public enum InterestManager implements Runnable {
continue;
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());
} else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) {
+11 -10
View File
@@ -14,6 +14,7 @@ import engine.mbEnums;
import engine.objects.Bane;
import engine.objects.City;
import engine.workthreads.WarehousePushThread;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.*;
@@ -33,8 +34,8 @@ public class BaneRecord extends DataRecord {
private String baneDropperHash;
private String baneGuildHash;
private String baneNationHash;
private LocalDateTime baneLiveTime;
private LocalDateTime baneDropTime;
private DateTime baneLiveTime;
private DateTime baneDropTime;
private BaneRecord(Bane bane) {
this.recordType = mbEnums.DataRecordType.BANE;
@@ -128,9 +129,9 @@ public class BaneRecord extends DataRecord {
return outStatement;
}
public static LocalDateTime getLastBaneDateTime(City city) {
public static DateTime getLastBaneDateTime(City city) {
LocalDateTime outDateTime = null;
DateTime outDateTime = null;
try (Connection connection = DbManager.getConnection();
PreparedStatement statement = buildDateTimeQueryStatement(connection, city);
@@ -138,7 +139,7 @@ public class BaneRecord extends DataRecord {
while (rs.next()) {
outDateTime = rs.getTimestamp("endDatetime").toLocalDateTime();
outDateTime = new DateTime(rs.getTimestamp("endDatetime"));
}
@@ -159,7 +160,7 @@ public class BaneRecord extends DataRecord {
}
public static void updateLiveDate(Bane bane, LocalDateTime dateTime) {
public static void updateLiveDate(Bane bane, DateTime dateTime) {
if (bane == null)
return;
@@ -174,13 +175,13 @@ public class BaneRecord extends DataRecord {
}
}
private static PreparedStatement buildUpdateLiveDateStatement(Connection connection, Bane bane, LocalDateTime dateTime) throws SQLException {
private static PreparedStatement buildUpdateLiveDateStatement(Connection connection, Bane bane, DateTime dateTime) throws SQLException {
PreparedStatement outStatement = null;
String queryString = "UPDATE `warehouse_banehistory` SET `liveDatetime` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'";
outStatement = connection.prepareStatement(queryString);
outStatement.setTimestamp(1, Timestamp.valueOf(dateTime));
outStatement.setTimestamp(1, new java.sql.Timestamp(dateTime.getMillis()));
outStatement.setString(2, bane.getCity().getHash());
return outStatement;
@@ -365,12 +366,12 @@ public class BaneRecord extends DataRecord {
if (this.baneDropTime == null)
outStatement.setNull(8, java.sql.Types.DATE);
else
outStatement.setTimestamp(8, Timestamp.valueOf(this.baneDropTime));
outStatement.setTimestamp(8, new java.sql.Timestamp(this.baneDropTime.getMillis()));
if (this.baneLiveTime == null)
outStatement.setNull(9, java.sql.Types.DATE);
else
outStatement.setTimestamp(9, Timestamp.valueOf(this.baneLiveTime));
outStatement.setTimestamp(9, new java.sql.Timestamp(this.baneLiveTime.getMillis()));
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.City;
import engine.objects.PlayerCharacter;
import java.time.LocalDateTime;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.Connection;
@@ -71,12 +71,12 @@ public class dbBaneHandler extends dbHandlerBase {
}
public boolean SET_BANE_TIME(LocalDateTime toSet, int cityUUID) {
public boolean SET_BANE_TIME(DateTime toSet, int cityUUID) {
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE `dyn_banes` SET `liveDate`=? WHERE `cityUUID`=?")) {
preparedStatement.setTimestamp(1, java.sql.Timestamp.valueOf(toSet));
preparedStatement.setTimestamp(1, new java.sql.Timestamp(toSet.getMillis()));
preparedStatement.setLong(2, cityUUID);
preparedStatement.execute();
@@ -17,9 +17,7 @@ import engine.mbEnums.DbObjectType;
import engine.mbEnums.ProtectionState;
import engine.mbEnums.TaxType;
import engine.objects.*;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.Connection;
@@ -42,7 +40,7 @@ public class dbBuildingHandler extends dbHandlerBase {
public Building CREATE_BUILDING(int parentZoneID, int OwnerUUID, String name, int meshUUID,
Vector3fImmutable location, float meshScale, int currentHP,
ProtectionState protectionState, int currentGold, int rank,
Instant upgradeDate, int blueprintUUID, float w, float rotY) {
DateTime upgradeDate, int blueprintUUID, float w, float rotY) {
Building building = null;
@@ -63,7 +61,7 @@ public class dbBuildingHandler extends dbHandlerBase {
preparedStatement.setInt(12, rank);
if (upgradeDate != null)
preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.toEpochMilli()));
preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis()));
else
preparedStatement.setNull(13, java.sql.Types.DATE);
@@ -751,7 +749,7 @@ public class dbBuildingHandler extends dbHandlerBase {
return false;
}
public boolean updateBuildingUpgradeTime(Instant upgradeDateTime, Building toUpgrade, int costToUpgrade) {
public boolean updateBuildingUpgradeTime(LocalDateTime upgradeDateTime, Building toUpgrade, int costToUpgrade) {
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET upgradeDate=?, currentGold=? "
@@ -760,7 +758,8 @@ public class dbBuildingHandler extends dbHandlerBase {
if (upgradeDateTime == null)
preparedStatement.setNull(1, java.sql.Types.DATE);
else
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.toEpochMilli()));
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.atZone(ZoneId.systemDefault())
.toInstant().toEpochMilli()));
preparedStatement.setInt(2, toUpgrade.getStrongboxValue() - costToUpgrade);
preparedStatement.setInt(3, toUpgrade.getObjectUUID());
@@ -839,7 +838,7 @@ public class dbBuildingHandler extends dbHandlerBase {
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_building SET taxDate=? "
+ "WHERE UID = ?")) {
preparedStatement.setTimestamp(1, new java.sql.Timestamp(Instant.now().plus(7, java.time.temporal.ChronoUnit.DAYS).toEpochMilli()));
preparedStatement.setTimestamp(1, new java.sql.Timestamp(DateTime.now().plusDays(7).getMillis()));
preparedStatement.setInt(2, building.getObjectUUID());
return (preparedStatement.executeUpdate() > 0);
+3 -3
View File
@@ -14,7 +14,7 @@ import engine.mbEnums;
import engine.mbEnums.GuildHistoryType;
import engine.objects.*;
import engine.server.world.WorldServer;
import java.time.Instant;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.*;
@@ -125,7 +125,7 @@ public class dbGuildHandler extends dbHandlerBase {
return false;
}
public boolean ADD_TO_GUILDHISTORY(int target, PlayerCharacter playerCharacter, Instant historyDate, GuildHistoryType historyType) {
public boolean ADD_TO_GUILDHISTORY(int target, PlayerCharacter playerCharacter, DateTime historyDate, GuildHistoryType historyType) {
try (Connection connection = DbManager.getConnection();
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)
preparedStatement.setNull(3, java.sql.Types.DATE);
else
preparedStatement.setTimestamp(3, java.sql.Timestamp.from(historyDate));
preparedStatement.setTimestamp(3, new Timestamp(historyDate.getMillis()));
preparedStatement.setString(4, historyType.name());
+3 -4
View File
@@ -12,8 +12,7 @@ package engine.db.handlers;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.objects.Mob;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.Connection;
@@ -83,7 +82,7 @@ public class dbMobHandler extends dbHandlerBase {
return mobileList;
}
public boolean updateUpgradeTime(Mob mob, LocalDateTime upgradeDateTime) {
public boolean updateUpgradeTime(Mob mob, DateTime upgradeDateTime) {
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_mob SET upgradeDate=? "
@@ -92,7 +91,7 @@ public class dbMobHandler extends dbHandlerBase {
if (upgradeDateTime == null)
preparedStatement.setNull(1, java.sql.Types.DATE);
else
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.getMillis()));
preparedStatement.setInt(2, mob.getObjectUUID());
+3 -4
View File
@@ -15,8 +15,7 @@ import engine.mbEnums;
import engine.mbEnums.ProfitType;
import engine.objects.NPC;
import engine.objects.NPCProfits;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.Connection;
@@ -241,7 +240,7 @@ public class dbNPCHandler extends dbHandlerBase {
}
}
public boolean updateUpgradeTime(NPC npc, LocalDateTime upgradeDateTime) {
public boolean updateUpgradeTime(NPC npc, DateTime upgradeDateTime) {
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE obj_npc SET upgradeDate=? "
@@ -250,7 +249,7 @@ public class dbNPCHandler extends dbHandlerBase {
if (upgradeDateTime == null)
preparedStatement.setNull(1, java.sql.Types.DATE);
else
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
preparedStatement.setTimestamp(1, new java.sql.Timestamp(upgradeDateTime.getMillis()));
preparedStatement.setInt(2, npc.getObjectUUID());
+3 -3
View File
@@ -16,7 +16,7 @@ import engine.mbEnums.ProtectionState;
import engine.objects.AbstractGameObject;
import engine.objects.Building;
import engine.objects.Shrine;
import java.time.Instant;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
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,
Vector3fImmutable location, float meshScale, int currentHP,
ProtectionState protectionState, int currentGold, int rank,
Instant upgradeDate, int blueprintUUID, float w, float rotY, String shrineType) {
DateTime upgradeDate, int blueprintUUID, float w, float rotY, String shrineType) {
ArrayList<AbstractGameObject> shrineList = new ArrayList<>();
@@ -74,7 +74,7 @@ public class dbShrineHandler extends dbHandlerBase {
preparedStatement.setInt(12, rank);
if (upgradeDate != null)
preparedStatement.setTimestamp(13, java.sql.Timestamp.from(upgradeDate));
preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis()));
else
preparedStatement.setNull(13, java.sql.Types.DATE);
@@ -20,7 +20,7 @@ import engine.objects.City;
import engine.objects.Transaction;
import engine.objects.Warehouse;
import engine.server.MBServerStatics;
import java.time.Instant;
import org.joda.time.DateTime;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
@@ -40,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, Instant date) {
public boolean CREATE_TRANSACTION(int warehouseBuildingID, GameObjectType targetType, int targetUUID, TransactionType transactionType, mbEnums.ResourceType resource, int amount, DateTime date) {
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO `dyn_warehouse_transactions` (`warehouseUID`, `targetType`,`targetUID`, `type`,`resource`,`amount`,`date` ) VALUES (?,?,?,?,?,?,?)")) {
@@ -51,7 +51,7 @@ public class dbWarehouseHandler extends dbHandlerBase {
preparedStatement.setString(4, transactionType.name());
preparedStatement.setString(5, resource.name());
preparedStatement.setInt(6, amount);
preparedStatement.setTimestamp(7, new java.sql.Timestamp(date.toEpochMilli()));
preparedStatement.setTimestamp(7, new java.sql.Timestamp(date.getMillis()));
return (preparedStatement.executeUpdate() > 0);
+1 -3
View File
@@ -25,7 +25,6 @@ import engine.objects.*;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
@@ -275,8 +274,7 @@ public enum BuildingManager {
if (building == null)
return;
Instant instant = (upgradeDateTime == null) ? null : upgradeDateTime.atZone(ZoneId.systemDefault()).toInstant();
if (!DbManager.BuildingQueries.updateBuildingUpgradeTime(instant, building, rankCost)) {
if (!DbManager.BuildingQueries.updateBuildingUpgradeTime(upgradeDateTime, building, rankCost)) {
Logger.error("Failed to set upgradeTime for building " + building.getObjectUUID());
return;
}
+110 -5
View File
@@ -14,6 +14,7 @@ import engine.jobs.AttackJob;
import engine.jobs.DeferredPowerJob;
import engine.mbEnums;
import engine.net.client.ClientConnection;
import engine.net.client.msg.PowerProjectileMsg;
import engine.net.client.msg.TargetedActionMsg;
import engine.net.client.msg.UpdateStateMsg;
import engine.objects.*;
@@ -146,11 +147,19 @@ public enum CombatManager {
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
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 (((Mob) attacker).isSiege())
attackRange = 300;
attackRange = getSiegeRange((Mob) attacker);
float distanceSquared = attacker.loc.distanceSquared(target.loc);
@@ -214,6 +223,22 @@ public enum CombatManager {
wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects.
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))
@@ -232,7 +257,8 @@ public enum CombatManager {
//handle retaliate
if (AbstractCharacter.IsAbstractCharacter(target)) {
//siege engines only retaliate on damage, see AbstractCharacter.modifyHealth
if (AbstractCharacter.IsAbstractCharacter(target) && !(target.getObjectType().equals(mbEnums.GameObjectType.Mob) && ((Mob) target).isSiege())) {
if (((AbstractCharacter) target).combatTarget == null || !((AbstractCharacter) target).combatTarget.isAlive()) {
((AbstractCharacter) target).combatTarget = attacker;
if (target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && ((AbstractCharacter) target).isCombat())
@@ -319,7 +345,7 @@ public enum CombatManager {
DispatchManager.sendToAllInRange(attacker, msg);
//we need to send the animation even if the attacker misses
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot));
DispatchManager.sendToAllInRange(target, cmm);
//set auto attack job
@@ -359,7 +385,7 @@ public enum CombatManager {
DispatchManager.dispatchMsgToInterestArea(target, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
//we need to send the animation even if the attacker misses
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot));
DispatchManager.sendToAllInRange(target, cmm);
//set auto attack job
setAutoAttackJob(attacker, slot, delay);
@@ -447,7 +473,7 @@ public enum CombatManager {
if (resists.immuneTo(damageType)) {
//set auto attack job
//we need to send the animation even if the attacker misses
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot));
DispatchManager.sendToAllInRange(target, cmm);
setAutoAttackJob(attacker, slot, delay);
return;
@@ -479,6 +505,15 @@ public enum CombatManager {
}
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) damage, attackAnim);
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);
}
}
}
@@ -636,6 +671,76 @@ 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) {
//damage calc for pet
float range;
+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.GuildInfoMsg;
import engine.objects.*;
import java.time.Instant;
import org.joda.time.DateTime;
public enum GuildManager {
@@ -67,8 +67,8 @@ public enum GuildManager {
return false;
if (playerCharacter.getGuild() != null && !playerCharacter.getGuild().isEmptyGuild()) {
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(playerCharacter.getGuildUUID(), playerCharacter, Instant.now(), GuildHistoryType.LEAVE)) {
GuildHistory guildHistory = new GuildHistory(playerCharacter.getGuildUUID(), playerCharacter.getGuild().getName(), Instant.now(), GuildHistoryType.LEAVE);
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(playerCharacter.getGuildUUID(), playerCharacter, DateTime.now(), GuildHistoryType.LEAVE)) {
GuildHistory guildHistory = new GuildHistory(playerCharacter.getGuildUUID(), playerCharacter.getGuild().getName(), DateTime.now(), GuildHistoryType.LEAVE);
playerCharacter.getGuildHistory().add(guildHistory);
}
}
@@ -90,8 +90,8 @@ public enum GuildManager {
dispatch = Dispatch.borrow(playerCharacter, msg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
}
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), playerCharacter, Instant.now(), historyType)) {
GuildHistory guildHistory = new GuildHistory(guild.getObjectUUID(), guild.getName(), Instant.now(), historyType);
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), playerCharacter, DateTime.now(), historyType)) {
GuildHistory guildHistory = new GuildHistory(guild.getObjectUUID(), guild.getName(), DateTime.now(), historyType);
playerCharacter.getGuildHistory().add(guildHistory);
}
+6 -3
View File
@@ -11,7 +11,7 @@ package engine.jobs;
import engine.job.AbstractScheduleJob;
import engine.objects.Bane;
import java.time.LocalDateTime;
import org.joda.time.DateTime;
public class BaneDefaultTimeJob extends AbstractScheduleJob {
@@ -31,8 +31,11 @@ public class BaneDefaultTimeJob extends AbstractScheduleJob {
return;
}
LocalDateTime defaultTime = this.bane.getPlacementDate()
.plusDays(2).withHour(22).withMinute(0).withSecond(0).withNano(0);
DateTime defaultTime = new DateTime(this.bane.getPlacementDate());
defaultTime = defaultTime.plusDays(2);
defaultTime = defaultTime.hourOfDay().setCopy(22);
defaultTime = defaultTime.minuteOfHour().setCopy(0);
defaultTime = defaultTime.secondOfMinute().setCopy(0);
this.bane.setLiveDate(defaultTime);
}
+22 -20
View File
@@ -824,6 +824,7 @@ public class mbEnums {
ACID,
DISEASE,
UNKNOWN,
ANTISIEGE,
// these added for immunities
ATTACK,
POWERS,
@@ -842,34 +843,26 @@ public class mbEnums {
public static DamageType getDamageType(String modName) {
String name;
if (modName.isEmpty())
return DamageType.NONE;
if(modName.toLowerCase().equals("blind"))
modName = "BLINDNESS";
// Client and DB strings vary in case and punctuation ("Fire", "Anti-Siege")
if(modName.toLowerCase().equals("powerblock"))
modName = "POWERINHIBITOR";
name = modName.replaceAll("[\\s,-]", "").toUpperCase();
//validity check for not looking up damage type that doesn't exist
boolean valid = false;
for(DamageType type : DamageType.values()){
if(type.name().equals(modName))
valid = true;
}
if (name.equals("BLIND"))
name = "BLINDNESS";
if(!valid)
return DamageType.NONE;
if (name.equals("POWERBLOCK"))
name = "POWERINHIBITOR";
DamageType damageType;
for (DamageType type : DamageType.values())
if (type.name().equals(name))
return type;
try {
damageType = DamageType.valueOf(modName.replace(",", "").toUpperCase());
} catch (Exception e) {
Logger.error(e);
return DamageType.NONE;
}
return damageType;
return DamageType.NONE;
}
}
@@ -1509,6 +1502,15 @@ public class mbEnums {
}
}
// rune_type values from the client cache data (COBJECTS/RUNE)
public enum RuneType {
CLASS,
DISCIPLINE,
RACE,
TALENT
}
public enum ShrineType {
Aelfborn(1701900, -75506007, true),
+50 -56
View File
@@ -14,12 +14,10 @@ import engine.gameManager.*;
import engine.math.Vector3f;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import engine.mbEnums.DispatchChannel;
import engine.mobileAI.Threads.MobAIThread;
import engine.mobileAI.Threads.ReSpawner;
import engine.mobileAI.utilities.MovementUtilities;
import engine.net.client.msg.PerformActionMsg;
import engine.net.client.msg.PowerProjectileMsg;
import engine.objects.*;
import engine.powers.ActionsBase;
import engine.powers.PowersBase;
@@ -46,6 +44,11 @@ public class MobAI {
//
// 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) {
try {
@@ -93,7 +96,7 @@ public class MobAI {
mob.updateLocation();
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget");
}
}
@@ -112,7 +115,9 @@ public class MobAI {
if (mob.behaviourType.callsForHelp)
mobCallForHelp(mob);
if (!MovementUtilities.inRangeDropAggro(mob, target)) {
// siege engines drop targets on their own attack range in determineAction
if (!mob.isSiege() && !MovementUtilities.inRangeDropAggro(mob, target)) {
mob.setCombatTarget(null);
return;
}
@@ -133,7 +138,7 @@ public class MobAI {
target.getPet().setCombatTarget(mob);
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackPlayer" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackPlayer");
}
}
@@ -155,20 +160,10 @@ public class MobAI {
if (guard.getCombatTarget() == null && !guard.getGuild().equals(mob.getGuild()))
guard.setCombatTarget(mob);
if (mob.isSiege())
MovementManager.sendRWSSMsg(mob);
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) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackBuilding" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackBuilding");
}
}
@@ -186,7 +181,7 @@ public class MobAI {
CombatManager.combatCycle(mob, target);
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackMob" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackMob");
}
}
@@ -248,7 +243,7 @@ public class MobAI {
MovementUtilities.aiMove(mob, mob.destination, true);
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: Patrol");
}
}
@@ -290,7 +285,7 @@ public class MobAI {
return mob.nextCastTime <= System.currentTimeMillis();
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: canCast" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: canCast");
}
return false;
}
@@ -387,7 +382,7 @@ public class MobAI {
return true;
}
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCast" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCast");
}
return false;
}
@@ -452,7 +447,7 @@ public class MobAI {
mob.nextCallForHelp = System.currentTimeMillis() + 60000;
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCallForHelp" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCallForHelp");
}
}
@@ -518,7 +513,16 @@ public class MobAI {
boolean bypassLoadedPlayerCheck = false;
if (mob.isPlayerGuard() || mob.isSiege()) {
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)
mob.setCombatTarget(null);
}
@@ -544,7 +548,7 @@ public class MobAI {
PlayerCharacter target = (PlayerCharacter) mob.getCombatTarget();
if (!mob.playerAgroMap.containsKey(target.getObjectUUID())) {
if (!mob.isSiege() && !mob.playerAgroMap.containsKey(target.getObjectUUID())) {
mob.setCombatTarget(null);
return;
}
@@ -575,7 +579,7 @@ public class MobAI {
break;
}
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: DetermineAction" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: DetermineAction");
}
}
@@ -630,7 +634,7 @@ public class MobAI {
}
}
if (aiAgent.getCombatTarget() == null) {
if (PET_AGGRO_ENABLED && aiAgent.getCombatTarget() == null) {
//look for pets to aggro if no players found to aggro
@@ -649,7 +653,7 @@ public class MobAI {
}
}
} catch (Exception e) {
Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForAggro" + " " + e.getMessage());
Logger.error(e, aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForAggro");
}
}
@@ -702,7 +706,7 @@ public class MobAI {
}
}
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckMobMovement" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckMobMovement");
}
}
@@ -746,7 +750,7 @@ public class MobAI {
ReSpawner.respawnQueue.put(aiAgent);
}
} catch (Exception e) {
Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn" + " " + e.getMessage());
Logger.error(e, aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn");
}
}
@@ -761,7 +765,7 @@ public class MobAI {
if (mob.getCombatTarget() == null)
return;
if (mob.getCombatTarget().getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && !MovementUtilities.inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) && !mob.agentType.equals(mbEnums.AIAgentType.PET)) {
if (mob.getCombatTarget().getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && !MovementUtilities.inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) && !mob.agentType.equals(mbEnums.AIAgentType.PET) && !mob.isSiege()) {
mob.setCombatTarget(null);
return;
@@ -769,7 +773,7 @@ public class MobAI {
attackTarget(mob, mob.getCombatTarget());
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForAttack" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForAttack");
}
}
@@ -812,7 +816,7 @@ public class MobAI {
mob.playerAgroMap.replaceAll((e, v) -> 0f);
}
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckToSendMobHome" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckToSendMobHome");
}
}
@@ -851,7 +855,7 @@ public class MobAI {
mob.updateMovementState();
mob.updateLocation();
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: chaseTarget" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: chaseTarget");
}
}
@@ -877,13 +881,17 @@ public class MobAI {
if (aggroMob.agentType.equals(mbEnums.AIAgentType.PET))
continue;
//don't attack siege engines
if (aggroMob.isSiege())
continue;
if (mob.getLoc().distanceSquared2D(aggroMob.getLoc()) > sqr(50))
continue;
mob.setCombatTarget(aggroMob);
}
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: SafeGuardAggro" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: SafeGuardAggro");
}
}
@@ -913,7 +921,7 @@ public class MobAI {
checkForAttack(mob); //only check to attack if combat target is not null
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardLogic" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardLogic");
}
}
@@ -946,7 +954,7 @@ public class MobAI {
mob.setHealth(mob.getHealthMax());
}
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: PetLogic" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: PetLogic");
}
}
@@ -970,7 +978,7 @@ public class MobAI {
}
checkForAttack(mob);
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: HamletGuardLogic" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: HamletGuardLogic");
}
}
@@ -1043,7 +1051,7 @@ public class MobAI {
checkForAttack(mob);
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: DefaultLogic" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: DefaultLogic");
}
}
@@ -1099,25 +1107,11 @@ public class MobAI {
return;
}
}
if (mob.getCombatTarget() == null) {
//look for siege equipment to aggro if no players found to aggro
// guards never auto-aggro siege engines; they only respond when one damages a city building
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) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForPlayerGuardAggro" + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForPlayerGuardAggro");
}
}
@@ -1180,7 +1174,7 @@ public class MobAI {
}
}
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro");
}
return false;
}
@@ -1232,7 +1226,7 @@ public class MobAI {
}
}
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: randomGuardPatrolPoints" + " " + e);
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: randomGuardPatrolPoints");
}
}
@@ -1267,7 +1261,7 @@ public class MobAI {
}
return mostHatedTarget;
} catch (Exception e) {
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: ChangeTargetFromMostHated" + " " + e.getMessage());
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: ChangeTargetFromMostHated");
}
return null;
}
+8 -9
View File
@@ -12,8 +12,7 @@ package engine.net;
import engine.math.Vector3f;
import engine.math.Vector3fImmutable;
import engine.util.ByteBufferUtils;
import java.time.Instant;
import java.time.ZoneId;
import org.joda.time.DateTime;
import java.nio.ByteBuffer;
import java.time.LocalDateTime;
@@ -58,13 +57,13 @@ public class ByteBufferWriter {
this.bb.putLong(x);
}
public synchronized void putDateTime(Instant dateTime) {
this.put((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalDate().getDayOfMonth());
this.put((byte) ((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalDate().getMonthValue() - 1));
this.putInt(dateTime.atZone(ZoneId.systemDefault()).toLocalDate().getYear() - 1900);
this.put((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalTime().getHour());
this.put((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalTime().getMinute());
this.put((byte) dateTime.atZone(ZoneId.systemDefault()).toLocalTime().getSecond());
public synchronized void putDateTime(DateTime dateTime) {
this.put((byte) dateTime.getDayOfMonth());
this.put((byte) ((byte) dateTime.getMonthOfYear() - 1));
this.putInt(dateTime.getYear() - 1900);
this.put((byte) dateTime.getHourOfDay());
this.put((byte) dateTime.getMinuteOfHour());
this.put((byte) dateTime.getSecondOfDay());
}
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.objects.PlayerCharacter;
import engine.server.MBServerStatics;
import java.time.Instant;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.lang.reflect.Constructor;
@@ -85,7 +85,7 @@ public class NetMsgFactory {
if (protocolMsg == Protocol.NONE) {
String errorString = Instant.now().toString() + origin.lastProtocol.name();
String errorString = DateTime.now().toString() + origin.lastProtocol.name();
int errorCode = errorString.hashCode();
@@ -22,7 +22,7 @@ import engine.net.client.msg.guild.GuildInfoMsg;
import engine.objects.Guild;
import engine.objects.GuildHistory;
import engine.objects.PlayerCharacter;
import java.time.Instant;
import org.joda.time.DateTime;
public class AcceptInviteToGuildHandler extends AbstractClientMsgHandler {
@@ -90,8 +90,8 @@ public class AcceptInviteToGuildHandler extends AbstractClientMsgHandler {
//Add to guild History
if (player.getGuild() != null) {
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(player.getGuildUUID(), player, Instant.now(), GuildHistoryType.JOIN)) {
GuildHistory guildHistory = new GuildHistory(player.getGuildUUID(), player.getGuild().getName(), Instant.now(), GuildHistoryType.JOIN);
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(player.getGuildUUID(), player, DateTime.now(), GuildHistoryType.JOIN)) {
GuildHistory guildHistory = new GuildHistory(player.getGuildUUID(), player.getGuild().getName(), DateTime.now(), GuildHistoryType.JOIN);
player.getGuildHistory().add(guildHistory);
}
}
@@ -25,7 +25,7 @@ import engine.objects.Guild;
import engine.objects.GuildHistory;
import engine.objects.GuildStatusController;
import engine.objects.PlayerCharacter;
import java.time.Instant;
import org.joda.time.DateTime;
public class BanishUnbanishHandler extends AbstractClientMsgHandler {
@@ -95,8 +95,8 @@ public class BanishUnbanishHandler extends AbstractClientMsgHandler {
//Set guild history
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), toBanish, Instant.now(), GuildHistoryType.BANISHED)) {
GuildHistory guildHistory = new GuildHistory(toBanish.getGuildUUID(), toBanish.getGuild().getName(), Instant.now(), GuildHistoryType.BANISHED);
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), toBanish, DateTime.now(), GuildHistoryType.BANISHED)) {
GuildHistory guildHistory = new GuildHistory(toBanish.getGuildUUID(), toBanish.getGuild().getName(), DateTime.now(), GuildHistoryType.BANISHED);
toBanish.getGuildHistory().add(guildHistory);
}
}
@@ -15,7 +15,7 @@ import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.ManageCityAssetsMsg;
import engine.net.client.msg.PlaceAssetMsg;
import engine.objects.*;
import java.time.LocalDateTime;
import org.joda.time.DateTime;
/*
* @Author:
@@ -251,10 +251,11 @@ public class ManageCityAssetMsgHandler extends AbstractClientMsgHandler {
return true;
}
LocalDateTime baneLive = bane.getPlacementDate();
DateTime baneLive = new DateTime(bane.getPlacementDate());
baneLive = baneHour == 24 ? baneLive.plusDays(3) : baneLive.plusDays(2);
baneLive = baneHour == 24 ? baneLive.withHour(0) : baneLive.withHour(baneHour);
baneLive = baneLive.withMinute(0).withSecond(1).withNano(0);
baneLive = baneHour == 24 ? baneLive.hourOfDay().setCopy(0) : baneLive.hourOfDay().setCopy(baneHour);
baneLive = baneLive.minuteOfHour().setCopy(0);
baneLive = baneLive.secondOfMinute().setCopy(1);
bane.setLiveDate(baneLive);
outMsg.actionType = 18;
@@ -19,7 +19,7 @@ import engine.net.client.msg.PlaceAssetMsg;
import engine.net.client.msg.PlaceAssetMsg.PlacementInfo;
import engine.objects.*;
import engine.server.MBServerStatics;
import java.time.Instant;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.time.LocalDateTime;
@@ -1142,7 +1142,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
Blueprint blueprint;
Building newMesh;
Instant completionDate;
DateTime completionDate;
float vendorRotation;
float buildingRotation;
@@ -1157,9 +1157,9 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
if ((blueprint.getBuildingGroup().equals(BuildingGroup.SIEGETENT))
|| (blueprint.getBuildingGroup().equals(BuildingGroup.BULWARK)))
completionDate = Instant.now().plusSeconds(15 * 60);
completionDate = DateTime.now().plusMinutes(15);
else
completionDate = Instant.now().plusSeconds(blueprint.getRankTime(1) * 3600);
completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));
Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));
@@ -1228,7 +1228,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
ArrayList<AbstractGameObject> shrineObjects = DbManager.ShrineQueries.CREATE_SHRINE(
currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
Instant.now().plusSeconds(blueprint.getRankTime(1) * 3600), blueprint.getMeshForRank(0), vendorRotation, buildingRotation, shrineType.name());
DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation, shrineType.name());
if (shrineObjects == null) {
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
@@ -1288,7 +1288,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
float buildingRotation = buildingInfo.getRot().y;
float vendorRotation = buildingInfo.getW();
Instant completionDate = Instant.now().plusSeconds(blueprint.getRankTime(1) * 3600);
DateTime completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));
newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
@@ -1333,7 +1333,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
warehouseBuilding = DbManager.BuildingQueries.CREATE_BUILDING(
currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
Instant.now().plusSeconds(blueprint.getRankTime(1) * 3600), blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
if (warehouseBuilding == null) {
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
@@ -17,8 +17,8 @@ import engine.net.Protocol;
import engine.objects.Building;
import engine.objects.Guild;
import engine.server.MBServerStatics;
import java.time.LocalDateTime;
import java.time.Duration;
import org.joda.time.DateTime;
import org.joda.time.Seconds;
public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
@@ -26,8 +26,8 @@ public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
private Building treeOfLife;
private int currentMineHour;
private Duration secondsLeft;
private LocalDateTime lateTime;
private Seconds secondsLeft;
private DateTime lateTime;
private int late;
/**
@@ -72,15 +72,15 @@ public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
currentMineHour = guild.getMineTime();
late = MBServerStatics.MINE_LATE_WINDOW;
lateTime = LocalDateTime.now();
lateTime = DateTime.now();
if (late == 0)
lateTime = lateTime.plusDays(1);
lateTime = lateTime.withHour(late);
lateTime = lateTime.hourOfDay().setCopy(late);
late = ((late > 23) ? (late - 24) : late);
secondsLeft = Duration.between(LocalDateTime.now(), lateTime);
secondsLeft = Seconds.secondsBetween(DateTime.now(), lateTime);
}
/**
@@ -106,4 +106,4 @@ public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
public void setBuildingUUID(int buildingUUID) {
this.buildingUUID = buildingUUID;
}
}
}
@@ -18,6 +18,9 @@ import engine.net.ByteBufferReader;
import engine.net.ByteBufferWriter;
import engine.net.Protocol;
import engine.objects.*;
import org.joda.time.DateTime;
import org.joda.time.Period;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Set;
@@ -394,33 +397,37 @@ public class ManageCityAssetsMsg extends ClientNetMsg {
else
writer.put((byte) 0);
LocalDateTime placedOn = bane.getLiveDate();
DateTime placedOn = bane.getLiveDate();
if (placedOn == null)
placedOn = LocalDateTime.now();
placedOn = new DateTime(DateTime.now());
//set Calander to date of bane live.
LocalDateTime now = LocalDateTime.now();
LocalDateTime defaultTime = bane.getPlacementDate();
LocalDateTime playerEnterWorldTime = LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(this.assetManager.getTimeStamp("EnterWorld")), ZoneId.systemDefault());
int hoursLoggedIn = (int) java.time.Duration.between(playerEnterWorldTime, now).toHours();
DateTime now = DateTime.now();
DateTime defaultTime = new DateTime(bane.getPlacementDate());
DateTime playerEnterWorldTime = new DateTime(this.assetManager.getTimeStamp("EnterWorld"));
Period period = new Period(playerEnterWorldTime.getMillis(), now.getMillis());
int hoursLoggedIn = period.getHours();
hoursLoggedIn = hoursLoggedIn < 0 ? 0 : hoursLoggedIn;
defaultTime = defaultTime.plusDays(2).withHour(22).withMinute(0).withSecond(0).withNano(0);
defaultTime = defaultTime.plusDays(2);
defaultTime = defaultTime.hourOfDay().setCopy(22);
defaultTime = defaultTime.minuteOfHour().setCopy(0);
defaultTime = defaultTime.secondOfMinute().setCopy(0);
long curTime = now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long curTime = now.getMillis();
long timeLeft = 0;
if (bane.getLiveDate() != null)
timeLeft = bane.getLiveDate().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - curTime;
timeLeft = bane.getLiveDate().getMillis() - curTime;
else
timeLeft = defaultTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - curTime + 1000;
timeLeft = defaultTime.getMillis() - curTime + 1000;
//DO not touch these. They are static formula's until i get the correct converter for SB Time.
writer.put((byte) placedOn.getDayOfMonth());
writer.put((byte) placedOn.getMonthValue());
writer.putInt(placedOn.getYear() - 1900);
writer.put((byte) placedOn.dayOfMonth().get());
writer.put((byte) placedOn.monthOfYear().get());
writer.putInt(placedOn.year().get() - 1900);
writer.put((byte) 0);
writer.put((byte) 0);
writer.put((byte) 0);
+24 -20
View File
@@ -19,8 +19,9 @@ import engine.net.ByteBufferReader;
import engine.net.ByteBufferWriter;
import engine.net.Protocol;
import engine.objects.*;
import java.time.Duration;
import java.time.LocalDateTime;
import org.joda.time.DateTime;
import org.joda.time.Period;
import org.joda.time.Seconds;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
@@ -136,6 +137,7 @@ public class ManageNPCMsg extends ClientNetMsg {
@Override
protected void _serialize(ByteBufferWriter writer) {
Period upgradePeriod;
int upgradePeriodInSeconds;
try {
@@ -197,20 +199,21 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(npc.getUpgradeCost());
long upgradeTotalSecs = 0;
if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(LocalDateTime.now()))
upgradeTotalSecs = Duration.between(LocalDateTime.now(), npc.getUpgradeDateTime()).getSeconds();
if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(DateTime.now()))
upgradePeriod = new Period(DateTime.now(), npc.getUpgradeDateTime());
else
upgradePeriod = new Period(0);
writer.put((byte) (int) (upgradeTotalSecs / 86400)); //for timer
writer.put((byte) upgradePeriod.getDays()); //for timer
writer.put((byte) unknown26);//unknown
writer.putInt(100); //unknown
writer.put((byte) (int) ((upgradeTotalSecs % 86400) / 3600)); //for timer
writer.put((byte) (int) ((upgradeTotalSecs % 3600) / 60)); //for timer
writer.put((byte) (int) (upgradeTotalSecs % 60)); //for timer
writer.put((byte) upgradePeriod.getHours()); //for timer
writer.put((byte) upgradePeriod.getMinutes()); //for timer
writer.put((byte) upgradePeriod.getSeconds()); //for timer
if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(LocalDateTime.now()))
upgradePeriodInSeconds = (int) upgradeTotalSecs;
if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(DateTime.now()))
upgradePeriodInSeconds = Seconds.secondsBetween(DateTime.now(), npc.getUpgradeDateTime()).getSeconds();
else
upgradePeriodInSeconds = 0;
@@ -586,20 +589,21 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(Mob.getUpgradeCost(mobA));
long upgradeTotalSecs = 0;
if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(LocalDateTime.now()))
upgradeTotalSecs = Duration.between(LocalDateTime.now(), mobA.getUpgradeDateTime()).getSeconds();
if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(DateTime.now()))
upgradePeriod = new Period(DateTime.now(), mobA.getUpgradeDateTime());
else
upgradePeriod = new Period(0);
writer.put((byte) (int) (upgradeTotalSecs / 86400)); //for timer
writer.put((byte) upgradePeriod.getDays()); //for timer
writer.put((byte) unknown26);//unknown
writer.putInt(100); //unknown
writer.put((byte) (int) ((upgradeTotalSecs % 86400) / 3600)); //for timer
writer.put((byte) (int) ((upgradeTotalSecs % 3600) / 60)); //for timer
writer.put((byte) (int) (upgradeTotalSecs % 60)); //for timer
writer.put((byte) upgradePeriod.getHours()); //for timer
writer.put((byte) upgradePeriod.getMinutes()); //for timer
writer.put((byte) upgradePeriod.getSeconds()); //for timer
if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(LocalDateTime.now()))
upgradePeriodInSeconds = (int) upgradeTotalSecs;
if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(DateTime.now()))
upgradePeriodInSeconds = Seconds.secondsBetween(DateTime.now(), mobA.getUpgradeDateTime()).getSeconds();
else
upgradePeriodInSeconds = 0;
@@ -17,7 +17,7 @@ import engine.net.ByteBufferWriter;
import engine.net.Protocol;
import engine.net.client.msg.ClientNetMsg;
import engine.objects.*;
import java.time.Instant;
import org.joda.time.DateTime;
public class GuildInfoMsg extends ClientNetMsg {
@@ -306,7 +306,7 @@ class GuildInfoMessageType4 extends GuildInfoMessageType {
writer.putString(guildLeaderName);
writer.putString(pc.getName());
writer.putDateTime(Instant.now());
writer.putDateTime(DateTime.now());
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(""); //Nation Leader - I believe
Instant now = Instant.now();
DateTime now = DateTime.now();
writer.putDateTime(now);
writer.put((byte) 1);
+90 -10
View File
@@ -656,12 +656,30 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD));
if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
Mob mob = (Mob) abstractCharacter;
abstractCharacter.minDamageHandOne = (int) mob.mobBase.getDamageMin();
abstractCharacter.minDamageHandTwo = (int) mob.mobBase.getDamageMin();
abstractCharacter.maxDamageHandOne = (int) mob.mobBase.getDamageMax();
abstractCharacter.maxDamageHandTwo = (int) mob.mobBase.getDamageMax();
abstractCharacter.atrHandOne = mob.mobBase.getAttackRating();
abstractCharacter.atrHandTwo = mob.mobBase.getAttackRating();
ItemTemplate rune = ItemTemplate.templates.get(mob.loadID);
float minDamage = mob.mobBase.getDamageMin();
float maxDamage = mob.mobBase.getDamageMax();
int attackRating = mob.mobBase.getAttackRating();
// The client (ArcMobile, 0x46D200) takes mob damage straight from the rune MINDAMAGE/MAXDAMAGE,
// 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();
}
}
@@ -717,9 +735,18 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
mastDam = CharacterSkill.getQuickMastery(abstractCharacter, "Unarmed Combat Mastery");
else
mastDam = masteryPercentage;
// TODO Correct these
min = 1;
max = 3;
// Base fist damage from the race rune natural attack (1-4 for all races), scaled below like a weapon.
// Matches the client's unarmed damage calc (0x46D200).
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 {
if (mainHand)
@@ -1476,13 +1503,22 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
Mob mob = (Mob) this;
if (mob.isSiege()) {
return 300;
return CombatManager.getSiegeRange(mob);
}
float range = 8;
if ((this).charItemManager.equipped.get(EquipSlotType.RHELD) != null) {
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.RHELD).template.item_weapon_max_range;
} else if (((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD) != null) {
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?
@@ -1827,6 +1863,15 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
if (this.getObjectType().equals(GameObjectType.Mob)) {
//handle hate value addition
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 (target.playerAgroMap.containsKey(attacker.getObjectUUID()))
@@ -1860,6 +1905,41 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
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() {
return this.health.get();
}
@@ -148,9 +148,20 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
public void dismiss() {
boolean isSiegeEngine;
isSiegeEngine = this.getObjectType() == GameObjectType.Mob && ((Mob) this).isSiege();
if (this.isPet()) {
if ((this.agentType.equals(mbEnums.AIAgentType.PET))) { //delete summoned pet
if (isSiegeEngine) {
// 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);
@@ -167,7 +178,8 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
// clear owner and set not alive
this.isAlive.set(false);
if (!isSiegeEngine)
this.isAlive.set(false);
PlayerCharacter owner = (PlayerCharacter) this.guardCaptain;
@@ -188,6 +200,8 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
this.guardCaptain = null;
}
if (isSiegeEngine && this.isAlive())
WorldGrid.updateObject(this);
}
}
+22 -18
View File
@@ -25,8 +25,7 @@ import engine.net.client.ClientConnection;
import engine.net.client.msg.PlaceAssetMsg;
import engine.net.client.msg.chat.ChatSystemMsg;
import engine.server.MBServerStatics;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
@@ -41,8 +40,8 @@ public final class Bane {
private final int cityUUID;
private final int stoneUUID;
private int ownerUUID;
private LocalDateTime placementDate = null;
private LocalDateTime liveDate = null;
private DateTime placementDate = null;
private DateTime liveDate = null;
private BaneDefaultTimeJob defaultTimeJob;
// Internal cache for banes
@@ -63,12 +62,12 @@ public final class Bane {
sqlDateTime = rs.getTimestamp("placementDate");
if (sqlDateTime != null)
this.placementDate = LocalDateTime.ofInstant(sqlDateTime.toInstant(), ZoneId.systemDefault());
this.placementDate = new DateTime(sqlDateTime);
sqlDateTime = rs.getTimestamp("liveDate");
if (sqlDateTime != null)
this.liveDate = LocalDateTime.ofInstant(sqlDateTime.toInstant(), ZoneId.systemDefault());
this.liveDate = new DateTime(sqlDateTime);
switch (this.getSiegePhase()) {
@@ -82,7 +81,7 @@ public final class Bane {
this.activateBaneJob.cancelJob();
abtj = new ActivateBaneJob(cityUUID);
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis());
this.activateBaneJob = abtj;
break;
@@ -94,7 +93,7 @@ public final class Bane {
this.activateBaneJob.cancelJob();
abtj = new ActivateBaneJob(cityUUID);
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis());
this.activateBaneJob = abtj;
break;
@@ -354,7 +353,7 @@ public final class Bane {
return phase;
}
if (LocalDateTime.now().isAfter(this.liveDate)) {
if (DateTime.now().isAfter(this.liveDate)) {
phase = SiegePhase.WAR; //war
return phase;
}
@@ -369,10 +368,15 @@ public final class Bane {
private void setDefaultTime() {
LocalDateTime timeToSetDefault = this.placementDate.plusDays(1);
DateTime timeToSetDefault = new DateTime(this.placementDate);
timeToSetDefault = timeToSetDefault.plusDays(1);
LocalDateTime currentTime = LocalDateTime.now();
LocalDateTime defaultTime = this.placementDate.plusDays(2).withHour(22).withMinute(0).withSecond(0);
DateTime currentTime = DateTime.now();
DateTime defaultTime = new DateTime(this.placementDate);
defaultTime = defaultTime.plusDays(2);
defaultTime = defaultTime.hourOfDay().setCopy(22);
defaultTime = defaultTime.minuteOfHour().setCopy(0);
defaultTime = defaultTime.secondOfMinute().setCopy(0);
if (currentTime.isAfter(timeToSetDefault))
this.setLiveDate(defaultTime);
@@ -382,7 +386,7 @@ public final class Bane {
this.defaultTimeJob.cancelJob();
BaneDefaultTimeJob bdtj = new BaneDefaultTimeJob(this);
JobScheduler.getInstance().scheduleJob(bdtj, timeToSetDefault.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
JobScheduler.getInstance().scheduleJob(bdtj, timeToSetDefault.getMillis());
this.defaultTimeJob = bdtj;
}
}
@@ -449,7 +453,7 @@ public final class Bane {
return true;
}
public final LocalDateTime getPlacementDate() {
public final DateTime getPlacementDate() {
return placementDate;
}
@@ -458,14 +462,14 @@ public final class Bane {
return (this.getSiegePhase() != SiegePhase.CHALLENGE);
}
public final LocalDateTime getLiveDate() {
public final DateTime getLiveDate() {
return liveDate;
}
public void setLiveDate(LocalDateTime baneTime) {
public void setLiveDate(DateTime baneTime) {
if (DbManager.BaneQueries.SET_BANE_TIME(baneTime, this.getCity().getObjectUUID())) {
this.liveDate = baneTime;
this.liveDate = new DateTime(baneTime);
// Push event to warehouse
@@ -507,7 +511,7 @@ public final class Bane {
ActivateBaneJob abtj = new ActivateBaneJob(cityUUID);
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis());
this.activateBaneJob = abtj;
} else {
Logger.debug("error with city " + this.getCity().getName());
+3 -1
View File
@@ -395,8 +395,10 @@ public class Building extends AbstractWorldObject {
//friendly building has been attacked, add attacker to city outlaw list
if (!playerCity.cityOutlaws.contains(attacker.getObjectUUID()) && attacker.getObjectType().equals(GameObjectType.PlayerCharacter))
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)
if (guard.combatTarget == null)
if (guard.isPlayerGuard() && guard.combatTarget == null)
guard.setCombatTarget(attacker);
}
}
+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.guild.GuildInfoMsg;
import engine.server.world.WorldServer;
import java.time.Instant;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
@@ -612,8 +612,8 @@ public class Guild extends AbstractWorldObject {
//Add to Guild History
if (pc.getGuild() != null) {
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(pc.getGuildUUID(), pc, Instant.now(), historyType)) {
GuildHistory guildHistory = new GuildHistory(pc.getGuildUUID(), pc.getGuild().name, Instant.now(), historyType);
if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(pc.getGuildUUID(), pc, DateTime.now(), historyType)) {
GuildHistory guildHistory = new GuildHistory(pc.getGuildUUID(), pc.getGuild().name, DateTime.now(), historyType);
pc.getGuildHistory().add(guildHistory);
}
}
+7 -8
View File
@@ -13,11 +13,10 @@ package engine.objects;
import engine.mbEnums.GameObjectType;
import engine.mbEnums.GuildHistoryType;
import engine.net.ByteBufferWriter;
import org.joda.time.DateTime;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class GuildHistory {
@@ -25,12 +24,12 @@ public class GuildHistory {
private int guildID;
private String guildName;
private Instant time;
private DateTime time;
private GuildHistoryType historyType;
public GuildHistory(int guildID, String guildName,
Instant dateTime, GuildHistoryType historyType) {
DateTime dateTime, GuildHistoryType historyType) {
super();
this.guildID = guildID;
this.guildName = guildName;
@@ -50,9 +49,9 @@ public class GuildHistory {
sqlDateTime = rs.getTimestamp("historyDate");
if (sqlDateTime != null)
this.time = sqlDateTime.toInstant();
this.time = new DateTime(sqlDateTime);
else
this.time = Instant.now().minus(1, ChronoUnit.YEARS);
this.time = DateTime.now().minusYears(1);
this.historyType = GuildHistoryType.valueOf(rs.getString("historyType"));
}
@@ -78,11 +77,11 @@ public class GuildHistory {
writer.putDateTime(this.time);
}
public Instant getTime() {
public DateTime getTime() {
return time;
}
public void setTime(Instant time) {
public void setTime(DateTime time) {
this.time = time;
}
+26
View File
@@ -90,8 +90,14 @@ public class ItemTemplate {
public final EnumSet<mbEnums.DisciplineType> item_disc_res = EnumSet.noneOf(mbEnums.DisciplineType.class);
public int item_level_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, 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<mbEnums.ResourceType, Integer> item_resource_cost = new HashMap<>();
public int modTable;
@@ -138,6 +144,11 @@ public class ItemTemplate {
public final EnumSet<mbEnums.MobBehaviourType> rune_group_role_set = EnumSet.noneOf(mbEnums.MobBehaviourType.class);
public boolean rune_renderable = false;
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, int[]> rune_skill_adj = new HashMap<>();
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
@@ -554,6 +565,21 @@ public class ItemTemplate {
rune_renderable = jsonObject.getBoolean("rune_renderable");
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");
for (String sparseType : rune_sparse_json.keySet()) {
+19 -9
View File
@@ -24,8 +24,7 @@ import engine.net.client.msg.PetMsg;
import engine.net.client.msg.PlaceAssetMsg;
import engine.server.MBServerStatics;
import org.jetbrains.annotations.NotNull;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
@@ -72,7 +71,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
public long nextAttackTime = System.currentTimeMillis();
private int lastMobPowerToken = 0;
private DeferredPowerJob weaponPower;
private LocalDateTime upgradeDateTime = null;
private DateTime upgradeDateTime = null;
private boolean lootSync = false;
// New Mobile constructor. Fill in the blanks and then call
@@ -127,7 +126,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
sqlDateTime = rs.getTimestamp("upgradeDate");
if (sqlDateTime != null)
upgradeDateTime = LocalDateTime.ofInstant(sqlDateTime.toInstant(), ZoneId.systemDefault());
upgradeDateTime = new DateTime(sqlDateTime);
else
upgradeDateTime = null;
@@ -553,6 +552,12 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
petMinion.despawned = false;
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);
petMinion.setLoc(petMinion.bindLoc);
@@ -594,8 +599,8 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
// Submit upgrade job for future date or current instant
if (mob.getUpgradeDateTime().isAfter(LocalDateTime.now()))
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), mob.getUpgradeDateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
if (mob.getUpgradeDateTime().isAfter(DateTime.now()))
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), mob.getUpgradeDateTime().getMillis());
else
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), 0);
@@ -621,7 +626,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
return upgradeCost;
}
public static void setUpgradeDateTime(Mob mob, LocalDateTime upgradeDateTime) {
public static void setUpgradeDateTime(Mob mob, DateTime upgradeDateTime) {
if (!DbManager.MobQueries.updateUpgradeTime(mob, upgradeDateTime)) {
Logger.error("Failed to set upgradeTime for building " + mob.currentID);
@@ -1319,6 +1324,11 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter))
return;
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth
if (this.isSiege())
return;
if (this.getCombatTarget() == null)
this.setCombatTarget(ac);
@@ -1354,7 +1364,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
this.weaponPower = weaponPower;
}
public LocalDateTime getUpgradeDateTime() {
public DateTime getUpgradeDateTime() {
lock.readLock().lock();
@@ -1434,7 +1444,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
if (!building.transferGold(-rankCost, false))
return;
LocalDateTime dateToUpgrade = LocalDateTime.now().plusHours(Mob.getUpgradeTime(this));
DateTime dateToUpgrade = DateTime.now().plusHours(Mob.getUpgradeTime(this));
Mob.setUpgradeDateTime(this, dateToUpgrade);
// Schedule upgrade job
+9 -10
View File
@@ -24,8 +24,7 @@ import engine.net.Dispatch;
import engine.net.client.msg.PetMsg;
import engine.net.client.msg.PlaceAssetMsg;
import engine.server.MBServerStatics;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
@@ -68,7 +67,7 @@ public class NPC extends AbstractCharacter {
protected int symbol;
// Variables NOT to be stored in db
protected boolean isStatic = false;
private LocalDateTime upgradeDateTime = null;
private DateTime upgradeDateTime = null;
private HashSet<Integer> canRoll = null;
public int parentZoneUUID;
public int equipmentSetID = 0;
@@ -132,7 +131,7 @@ public class NPC extends AbstractCharacter {
sqlDateTime = rs.getTimestamp("upgradeDate");
if (sqlDateTime != null)
upgradeDateTime = LocalDateTime.ofInstant(sqlDateTime.toInstant(), ZoneId.systemDefault());
upgradeDateTime = new DateTime(sqlDateTime);
else
upgradeDateTime = null;
@@ -528,9 +527,9 @@ public class NPC extends AbstractCharacter {
// Submit upgrade job for future date or current instant
if (this.getUpgradeDateTime().isAfter(LocalDateTime.now()))
if (this.getUpgradeDateTime().isAfter(DateTime.now()))
jc = JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this),
this.getUpgradeDateTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
this.getUpgradeDateTime().getMillis());
else
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), 0);
@@ -908,7 +907,7 @@ public class NPC extends AbstractCharacter {
return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
}
public LocalDateTime getUpgradeDateTime() {
public DateTime getUpgradeDateTime() {
lock.readLock().lock();
try {
return upgradeDateTime;
@@ -917,7 +916,7 @@ public class NPC extends AbstractCharacter {
}
}
public void setUpgradeDateTime(LocalDateTime upgradeDateTime) {
public void setUpgradeDateTime(DateTime upgradeDateTime) {
if (!DbManager.NPCQueries.updateUpgradeTime(this, upgradeDateTime)) {
Logger.error("Failed to set upgradeTime for building " + currentID);
@@ -1060,7 +1059,7 @@ public class NPC extends AbstractCharacter {
int rankCost;
Building building;
LocalDateTime dateToUpgrade;
DateTime dateToUpgrade;
this.lock.writeLock().lock();
@@ -1103,7 +1102,7 @@ public class NPC extends AbstractCharacter {
if (!building.transferGold(-rankCost, false))
return;
dateToUpgrade = LocalDateTime.now().plusHours(this.getUpgradeTime());
dateToUpgrade = DateTime.now().plusHours(this.getUpgradeTime());
this.setUpgradeDateTime(dateToUpgrade);
+6 -7
View File
@@ -37,8 +37,7 @@ import engine.server.MBServerStatics;
import engine.server.login.LoginServer;
import engine.server.world.WorldServer;
import engine.util.MiscUtils;
import java.time.Instant;
import java.time.ZoneId;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
@@ -703,11 +702,11 @@ public class PlayerCharacter extends AbstractCharacter {
writer.putInt(token);
writer.putInt((int) timeLeft / 1000);
}
Instant enterWorld = Instant.ofEpochMilli(playerCharacter.timestamps.get("EnterWorld"));
DateTime enterWorld = new DateTime(playerCharacter.timestamps.get("EnterWorld"));
writer.putDateTime(enterWorld);
writer.putInt(0x49EF1E98); //DUnno what playerCharacter is.
writer.putFloat(Instant.now().atZone(ZoneId.systemDefault()).toLocalTime().getHour()); //daylight in float.
writer.putFloat(DateTime.now().hourOfDay().get()); //daylight in 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
} else {
@@ -4185,8 +4184,9 @@ public class PlayerCharacter extends AbstractCharacter {
} else if (currentPet.isSiege()) {
currentPet.agentType = AIAgentType.MOBILE;
// released siege engine keeps its current target
currentPet.guardCaptain = null;
currentPet.setCombatTarget(null);
if (currentPet.isAlive())
WorldGrid.updateObject(currentPet);
}
@@ -4194,7 +4194,6 @@ public class PlayerCharacter extends AbstractCharacter {
toCommand.setPet(this, false);
this.setPet(toCommand);
toCommand.setCombatTarget(null);
PetMsg petMsg = new PetMsg(6, toCommand);
Dispatch dispatch = Dispatch.borrow(this, petMsg);
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
@@ -4365,7 +4364,7 @@ public class PlayerCharacter extends AbstractCharacter {
}
if (count > 0) {
this.timestamps.put("RealmClaim", Instant.now().plusSeconds(180).toEpochMilli());
this.timestamps.put("RealmClaim", DateTime.now().plusMinutes(3).getMillis());
for (PlayerCharacter toSend : SessionManager.getAllActivePlayerCharacters()) {
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.GameObjectType;
import engine.mbEnums.TransactionType;
import java.time.Instant;
import org.joda.time.DateTime;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -24,7 +24,7 @@ public class Transaction implements Comparable<Transaction> {
private final int warehouseUUID;
private final int targetUUID;
private final mbEnums.ResourceType resource;
private final Instant date;
private final DateTime date;
private final int amount;
private final TransactionType transactionType;
private GameObjectType targetType;
@@ -41,15 +41,15 @@ public class Transaction implements Comparable<Transaction> {
Date sqlDateTime = rs.getTimestamp("date");
if (sqlDateTime != null)
this.date = sqlDateTime.toInstant();
this.date = new DateTime(sqlDateTime);
else
this.date = Instant.now();
this.date = DateTime.now();
}
public Transaction(int warehouseUUID, GameObjectType targetType, int targetUUID, TransactionType transactionType, mbEnums.ResourceType resource, int amount,
Instant date) {
DateTime date) {
this.warehouseUUID = warehouseUUID;
this.targetUUID = targetUUID;
this.resource = resource;
@@ -75,7 +75,7 @@ public class Transaction implements Comparable<Transaction> {
}
public Instant getDate() {
public DateTime getDate() {
return date;
}
+3 -3
View File
@@ -18,7 +18,7 @@ import engine.net.Dispatch;
import engine.net.client.ClientConnection;
import engine.net.client.msg.*;
import engine.server.MBServerStatics;
import java.time.Instant;
import org.joda.time.DateTime;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
@@ -576,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) {
if (!DbManager.WarehouseQueries.CREATE_TRANSACTION(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, Instant.now()))
if (!DbManager.WarehouseQueries.CREATE_TRANSACTION(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, DateTime.now()))
return;
Transaction transaction = new Transaction(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, Instant.now());
Transaction transaction = new Transaction(warehouse.building.getObjectUUID(), targetType, targetUUID, transactionType, resource, amount, DateTime.now());
warehouse.transactions.add(transaction);
}
+307
View File
@@ -0,0 +1,307 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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"));
if (jsonFiles == null) {
Logger.error("Cache folder not found: " + folder.getAbsolutePath());
return new File[0];
}
return jsonFiles;
}
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 are logged and returned as null so one bad value
// doesn't drop the whole template.
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) {
Logger.error(templateId + ": " + fieldName + " has no " + enumClass.getSimpleName() + " for '" + value + "'");
return null;
}
}
public static <E extends Enum<E>> EnumSet<E> readEnumSet(Class<E> enumClass, JSONArray valuesJson, int templateId, String fieldName) {
EnumSet<E> enumSet = EnumSet.noneOf(enumClass);
E enumValue;
for (int i = 0; i < valuesJson.length(); i++) {
enumValue = readEnum(enumClass, valuesJson.getString(i), templateId, fieldName);
if (enumValue != null)
enumSet.add(enumValue);
}
return enumSet;
}
// [{keyName: "Strength", valueName: 40}, ...] -> enum map
// Negative values are stored unsigned in the JSON (-5 is 4294967291),
// so values are read as long and wrapped to int.
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;
E enumValue;
for (int i = 0; i < entriesJson.length(); i++) {
entry = entriesJson.getJSONObject(i);
enumValue = readEnum(enumClass, entry.getString(keyName), templateId, fieldName);
if (enumValue != null)
enumMap.put(enumValue, (int) entry.getLong(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);
E enumValue;
for (String key : mapJson.keySet()) {
enumValue = readEnum(enumClass, key, templateId, fieldName);
if (enumValue != null)
enumMap.put(enumValue, mapJson.getFloat(key));
}
return enumMap;
}
// Values are read as long and wrapped to int so unsigned 32-bit hashes survive.
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 "";
}
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;
}
public static ArrayList<PowerGrant> readPowerGrants(JSONArray grantsJson) {
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 = entry.getJSONArray("power_granted_attrs");
powerGrant.power_granted_skills.addAll(readIntArrays(entry.getJSONArray("power_granted_skills")));
powerGrant.power_granted_powers.addAll(readIntArrays(entry.getJSONArray("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;
}
}
+179
View File
@@ -0,0 +1,179 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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).
// Not yet wired into server startup.
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;
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);
}
}
Logger.info("Loaded " + deedTemplates.size() + " of " + deedFiles.length + " deed templates");
return deedTemplates;
}
private static DeedTemplate parseDeedEntry(int templateId, JSONObject deedJson) {
DeedTemplate deed = new DeedTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
String flag;
mbEnums.ItemFlags itemFlag;
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;
itemFlag = CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags");
if (itemFlag != null)
deed.item_flags.add(itemFlag);
}
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")));
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;
}
}
+333
View File
@@ -0,0 +1,333 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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).
// Not yet wired into server startup.
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;
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);
}
}
Logger.info("Loaded " + runeTemplates.size() + " of " + runeFiles.length + " rune templates");
return runeTemplates;
}
private static RuneTemplate parseRuneEntry(int templateId, JSONObject runeJson) {
RuneTemplate rune = new RuneTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
String flag;
mbEnums.ItemFlags itemFlag;
Object subType;
JSONObject speedJson;
mbEnums.MovementType movementType;
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;
itemFlag = CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags");
if (itemFlag != null)
rune.item_flags.add(itemFlag);
}
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")));
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()) {
movementType = CacheParserUtil.readEnum(mbEnums.MovementType.class, speedKey, templateId, "rune_speed");
if (movementType != null)
rune.rune_speed.put(movementType, 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 = skillGrantJson.getJSONArray("skill_granted_attrs");
skillGrant.skill_granted_skills.addAll(CacheParserUtil.readIntArrays(skillGrantJson.getJSONArray("skill_granted_skills")));
skillGrant.skill_granted_powers = skillGrantJson.getJSONArray("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;
}
}
@@ -0,0 +1,120 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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 {
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;
}
@@ -0,0 +1,33 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import org.json.JSONArray;
import java.util.ArrayList;
// One entry of item_power_grant.
//
// power_granted_skills and power_granted_powers entries look like
// [hash, value] pairs (first element is a 32-bit string hash, some
// exceed Integer.MAX_VALUE in the JSON and are stored wrapped to int).
// This reading is from the data only and has not been confirmed in the client.
//
// power_granted_attrs and power_monster_types are empty in every DEED and
// RUNE file, so their shape is unknown and they are kept as raw JSON.
public class PowerGrant {
public String power_type;
public int power_value;
public JSONArray power_granted_attrs;
public final ArrayList<int[]> power_granted_skills = new ArrayList<>();
public final ArrayList<int[]> power_granted_powers = new ArrayList<>();
public JSONArray power_monster_types;
}
@@ -0,0 +1,225 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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 {
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.
// skill_granted_skills entries look like [hash, value] pairs; not confirmed in the client.
// skill_granted_attrs, skill_granted_powers and skill_monster_types are empty
// in every RUNE file, so they are kept as raw JSON.
public static class SkillGrant {
public String skill_type;
public int skill_value;
public JSONArray skill_granted_attrs;
public final ArrayList<int[]> skill_granted_skills = new ArrayList<>();
public JSONArray skill_granted_powers;
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;
}
}
@@ -0,0 +1,24 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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
@@ -0,0 +1,11 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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,7 +246,9 @@ public class ApplyEffectPowerAction extends AbstractPowerAction {
if (this.effectID.equals("TAUNT")) {
if (awo != null && awo.getObjectType() == GameObjectType.Mob) {
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth
if (awo != null && awo.getObjectType() == GameObjectType.Mob && !((Mob) awo).isSiege()) {
((Mob) awo).setCombatTarget(source);
ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
DispatchManager.sendToAllInRange(source, msg);
+2 -1
View File
@@ -18,6 +18,7 @@ import engine.net.MessageDispatcher;
import engine.net.client.msg.chat.ChatSystemMsg;
import engine.objects.*;
import engine.server.world.WorldServer;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.time.LocalDateTime;
@@ -291,7 +292,7 @@ public class HourlyJobThread implements Runnable {
public static void processBanes() {
//handle banes
for (Bane bane : Bane.banes.values()) {
if (bane.getLiveDate() != null && LocalDateTime.now().isAfter(bane.getLiveDate().minusMinutes(1)) && bane.isStarted == false)
if (bane.getLiveDate() != null && DateTime.now().isAfter(bane.getLiveDate().minusMinutes(1)) && bane.isStarted == false)
bane.startBane();
}
}