Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbf978872b | ||
|
|
e288524ec0 | ||
|
|
bb76596a9a | ||
|
|
2078a5c344 | ||
|
|
74b2eaaaf2 | ||
|
|
c9474cf042 | ||
|
|
99e384cfe1 | ||
|
|
03399bece7 | ||
|
|
35f1738ee4 | ||
|
|
015d536337 | ||
|
|
d586536c74 |
No files matched your search
@@ -28,6 +28,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public class dbBuildingHandler extends dbHandlerBase {
|
public class dbBuildingHandler extends dbHandlerBase {
|
||||||
@@ -37,52 +38,45 @@ public class dbBuildingHandler extends dbHandlerBase {
|
|||||||
this.localObjectType = mbEnums.GameObjectType.valueOf(this.localClass.getSimpleName());
|
this.localObjectType = mbEnums.GameObjectType.valueOf(this.localClass.getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Building CREATE_BUILDING(int parentZoneID, int OwnerUUID, String name, int meshUUID,
|
public int PERSIST(Building building) {
|
||||||
Vector3fImmutable location, float meshScale, int currentHP,
|
|
||||||
ProtectionState protectionState, int currentGold, int rank,
|
|
||||||
DateTime upgradeDate, int blueprintUUID, float w, float rotY) {
|
|
||||||
|
|
||||||
Building building = null;
|
int objectUUID = 0;
|
||||||
|
|
||||||
try (Connection connection = DbManager.getConnection();
|
try (Connection connection = DbManager.getConnection();
|
||||||
PreparedStatement preparedStatement = connection.prepareStatement("CALL `building_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?);")) {
|
PreparedStatement preparedStatement = connection.prepareStatement("CALL `building_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?);")) {
|
||||||
|
|
||||||
preparedStatement.setInt(1, parentZoneID);
|
preparedStatement.setInt(1, building.parentZoneUUID);
|
||||||
preparedStatement.setInt(2, OwnerUUID);
|
preparedStatement.setInt(2, building.getOwnerUUID());
|
||||||
preparedStatement.setString(3, name);
|
preparedStatement.setString(3, building.getName());
|
||||||
preparedStatement.setInt(4, meshUUID);
|
preparedStatement.setInt(4, building.meshUUID);
|
||||||
preparedStatement.setFloat(5, location.x);
|
preparedStatement.setFloat(5, building.statLat);
|
||||||
preparedStatement.setFloat(6, location.y);
|
preparedStatement.setFloat(6, building.statAlt);
|
||||||
preparedStatement.setFloat(7, location.z);
|
preparedStatement.setFloat(7, building.statLon);
|
||||||
preparedStatement.setFloat(8, meshScale);
|
preparedStatement.setFloat(8, building.getMeshScale().x);
|
||||||
preparedStatement.setInt(9, currentHP);
|
preparedStatement.setInt(9, (int) building.getHealth());
|
||||||
preparedStatement.setString(10, protectionState.name());
|
preparedStatement.setString(10, building.getProtectionState().name());
|
||||||
preparedStatement.setInt(11, currentGold);
|
preparedStatement.setInt(11, building.getStrongboxValue());
|
||||||
preparedStatement.setInt(12, rank);
|
preparedStatement.setInt(12, building.getRank());
|
||||||
|
|
||||||
if (upgradeDate != null)
|
if (building.upgradeDateTime != null)
|
||||||
preparedStatement.setTimestamp(13, new java.sql.Timestamp(upgradeDate.getMillis()));
|
preparedStatement.setTimestamp(13, java.sql.Timestamp.valueOf(building.upgradeDateTime));
|
||||||
else
|
else
|
||||||
preparedStatement.setNull(13, java.sql.Types.DATE);
|
preparedStatement.setNull(13, java.sql.Types.DATE);
|
||||||
|
|
||||||
preparedStatement.setInt(14, blueprintUUID);
|
preparedStatement.setInt(14, building.getBlueprintUUID());
|
||||||
preparedStatement.setFloat(15, w);
|
preparedStatement.setFloat(15, building.getRotation().w);
|
||||||
preparedStatement.setFloat(16, rotY);
|
preparedStatement.setFloat(16, building.getRotation().y);
|
||||||
|
|
||||||
ResultSet rs = preparedStatement.executeQuery();
|
ResultSet rs = preparedStatement.executeQuery();
|
||||||
|
|
||||||
if (rs.next()) {
|
if (rs.next())
|
||||||
int objectUUID = (int) rs.getLong("UID");
|
objectUUID = (int) rs.getLong("UID");
|
||||||
|
|
||||||
if (objectUUID > 0)
|
|
||||||
building = GET_BUILDINGBYUUID(objectUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new RuntimeException(e);
|
Logger.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return building;
|
return objectUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean DELETE_FROM_DATABASE(final Building b) {
|
public boolean DELETE_FROM_DATABASE(final Building b) {
|
||||||
@@ -107,6 +101,26 @@ public class dbBuildingHandler extends dbHandlerBase {
|
|||||||
return buildings;
|
return buildings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashSet<Integer> GET_NPC_BUILDING_OWNERS() {
|
||||||
|
|
||||||
|
HashSet<Integer> npcOwners = new HashSet<>();
|
||||||
|
|
||||||
|
try (Connection connection = DbManager.getConnection();
|
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement("SELECT DISTINCT `obj_building`.`ownerUUID` FROM `obj_building` INNER JOIN `object` ON `object`.`UID` = `obj_building`.`ownerUUID` WHERE `object`.`type` = 'npc';")) {
|
||||||
|
|
||||||
|
ResultSet rs = preparedStatement.executeQuery();
|
||||||
|
|
||||||
|
while (rs.next())
|
||||||
|
npcOwners.add(rs.getInt("ownerUUID"));
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
Logger.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info("read: " + npcOwners.size() + " NPC building owners");
|
||||||
|
return npcOwners;
|
||||||
|
}
|
||||||
|
|
||||||
public Building GET_BUILDINGBYUUID(int uuid) {
|
public Building GET_BUILDINGBYUUID(int uuid) {
|
||||||
|
|
||||||
if (uuid == 0)
|
if (uuid == 0)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import engine.devcmd.AbstractDevCmd;
|
|||||||
import engine.gameManager.ChatManager;
|
import engine.gameManager.ChatManager;
|
||||||
import engine.gameManager.DbManager;
|
import engine.gameManager.DbManager;
|
||||||
import engine.gameManager.ZoneManager;
|
import engine.gameManager.ZoneManager;
|
||||||
import engine.math.Vector3f;
|
import engine.math.Quaternion;
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.mbEnums.ProtectionState;
|
import engine.mbEnums.ProtectionState;
|
||||||
import engine.objects.*;
|
import engine.objects.*;
|
||||||
@@ -54,8 +54,7 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
|||||||
"Invalid addBuilding Command. Invalid Building ID.");
|
"Invalid addBuilding Command. Invalid Building ID.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f);
|
Quaternion rotation = new Quaternion();
|
||||||
float w = 1f;
|
|
||||||
Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
|
Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
|
||||||
|
|
||||||
if (zone == null) {
|
if (zone == null) {
|
||||||
@@ -73,8 +72,7 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
|||||||
Building likeBuilding = DbManager.BuildingQueries.GET_BUILDING_BY_MESH(ID);
|
Building likeBuilding = DbManager.BuildingQueries.GET_BUILDING_BY_MESH(ID);
|
||||||
|
|
||||||
if (likeBuilding != null) {
|
if (likeBuilding != null) {
|
||||||
rot = likeBuilding.getRot();
|
rotation = likeBuilding.getRotation();
|
||||||
w = likeBuilding.getw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String buildingName = "";
|
String buildingName = "";
|
||||||
@@ -90,11 +88,10 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
|||||||
blueprintUUID = blueprint.getBlueprintUUID();
|
blueprintUUID = blueprint.getBlueprintUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
Building building = DbManager.BuildingQueries.
|
Building building = Building.persistBuilding(Building.createBuilding(
|
||||||
CREATE_BUILDING(
|
zone, 0, buildingName, ID,
|
||||||
zone.getObjectUUID(), 0, buildingName, ID,
|
|
||||||
localLoc, 1.0f, 0, ProtectionState.PROTECTED, 0, rank,
|
localLoc, 1.0f, 0, ProtectionState.PROTECTED, 0, rank,
|
||||||
null, blueprintUUID, w, rot.y);
|
null, blueprintUUID, rotation.w, rotation.y));
|
||||||
|
|
||||||
|
|
||||||
if (building == null) {
|
if (building == null) {
|
||||||
@@ -102,9 +99,6 @@ public class AddBuildingCmd extends AbstractDevCmd {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
building.setRot(rot);
|
|
||||||
building.setw(w);
|
|
||||||
|
|
||||||
building.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
|
building.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
|
||||||
WorldGrid.addObject(building, pc);
|
WorldGrid.addObject(building, pc);
|
||||||
ChatManager.chatSayInfo(pc,
|
ChatManager.chatSayInfo(pc,
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class InfoCmd extends AbstractDevCmd {
|
|||||||
output += newline;
|
output += newline;
|
||||||
output += StringUtils.addWS("Alt: " + targetLoc.y, 20);
|
output += StringUtils.addWS("Alt: " + targetLoc.y, 20);
|
||||||
output += newline;
|
output += newline;
|
||||||
output += "Rot: " + targetAWO.getRot().y;
|
output += "Rot: " + targetAWO.getRotation().y;
|
||||||
output += newline;
|
output += newline;
|
||||||
double radian = 0;
|
double radian = 0;
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ public class InfoCmd extends AbstractDevCmd {
|
|||||||
case Building:
|
case Building:
|
||||||
Building targetBuilding = (Building) target;
|
Building targetBuilding = (Building) target;
|
||||||
output += StringUtils.addWS("Lac: "
|
output += StringUtils.addWS("Lac: "
|
||||||
+ targetBuilding.getw(), 20);
|
+ targetBuilding.getRotation().w, 20);
|
||||||
output += "Blueprint : ";
|
output += "Blueprint : ";
|
||||||
output += targetBuilding.getBlueprintUUID();
|
output += targetBuilding.getBlueprintUUID();
|
||||||
output += newline;
|
output += newline;
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import engine.devcmd.AbstractDevCmd;
|
|||||||
import engine.gameManager.BuildingManager;
|
import engine.gameManager.BuildingManager;
|
||||||
import engine.gameManager.ChatManager;
|
import engine.gameManager.ChatManager;
|
||||||
import engine.gameManager.DbManager;
|
import engine.gameManager.DbManager;
|
||||||
import engine.math.Vector3f;
|
import engine.math.FastMath;
|
||||||
|
import engine.math.Quaternion;
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.objects.*;
|
import engine.objects.*;
|
||||||
|
|
||||||
@@ -63,14 +64,12 @@ public class RotateCmd extends AbstractDevCmd {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3f rotation = new Vector3f(0f, rot, 0f);
|
|
||||||
|
|
||||||
if (target instanceof Building)
|
if (target instanceof Building)
|
||||||
rotateBuilding(pc, (Building) target, rotation, rot, false);
|
rotateBuilding(pc, (Building) target, (float) Math.toRadians(rot));
|
||||||
else if (target instanceof NPC)
|
else if (target instanceof NPC)
|
||||||
rotateNPC(pc, (NPC) target, rotation, false);
|
rotateNPC(pc, (NPC) target, (float) Math.toRadians(rot));
|
||||||
else if (target instanceof Mob)
|
else if (target instanceof Mob)
|
||||||
rotateMob(pc, (Mob) target, rotation, false);
|
rotateMob(pc, (Mob) target, (float) Math.toRadians(rot));
|
||||||
else
|
else
|
||||||
throwbackError(pc, "Target " + target.getObjectType()
|
throwbackError(pc, "Target " + target.getObjectType()
|
||||||
+ " is not a valid object type");
|
+ " is not a valid object type");
|
||||||
@@ -141,15 +140,13 @@ public class RotateCmd extends AbstractDevCmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3f rotation = new Vector3f(0f, rot, 0f);
|
|
||||||
|
|
||||||
Building b;
|
Building b;
|
||||||
if (id != 0)
|
if (id != 0)
|
||||||
b = BuildingManager.getBuilding(id);
|
b = BuildingManager.getBuilding(id);
|
||||||
else
|
else
|
||||||
b = getTargetAsBuilding(pc);
|
b = getTargetAsBuilding(pc);
|
||||||
if (b != null) {
|
if (b != null) {
|
||||||
rotateBuilding(pc, b, rotation, rot, false);
|
rotateBuilding(pc, b, (float) Math.toRadians(rot));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +157,7 @@ public class RotateCmd extends AbstractDevCmd {
|
|||||||
else
|
else
|
||||||
npc = getTargetAsNPC(pc);
|
npc = getTargetAsNPC(pc);
|
||||||
if (npc != null) {
|
if (npc != null) {
|
||||||
rotateNPC(pc, npc, rotation, false);
|
rotateNPC(pc, npc, (float) Math.toRadians(rot));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +168,7 @@ public class RotateCmd extends AbstractDevCmd {
|
|||||||
else
|
else
|
||||||
mob = getTargetAsMob(pc);
|
mob = getTargetAsMob(pc);
|
||||||
if (mob != null) {
|
if (mob != null) {
|
||||||
rotateMob(pc, mob, rotation, false);
|
rotateMob(pc, mob, (float) Math.toRadians(rot));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throwbackError(pc, "Nothing found to rotate.");
|
throwbackError(pc, "Nothing found to rotate.");
|
||||||
@@ -185,40 +182,40 @@ public class RotateCmd extends AbstractDevCmd {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String _getUsageString() {
|
protected String _getUsageString() {
|
||||||
return "' /rotate [objectID] rotation' || ' /rot [objectID] rotation'";
|
return "' /rotate [objectID] degrees' || ' /rot [objectID] degrees' || ' /rotate face'";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rotateBuilding(PlayerCharacter pc, Building building, Vector3f rot, float orig, boolean faceDirection) {
|
private void rotateBuilding(PlayerCharacter pc, Building building, float yaw) {
|
||||||
if (!faceDirection)
|
building.setRotation(Quaternion.fromYaw(yaw));
|
||||||
rot.set(0.0f, (float) Math.sin(Math.toRadians(orig) / 2), 0.0f);
|
|
||||||
building.setRot(rot);
|
|
||||||
building.setw((float) Math.abs(Math.cos(Math.toRadians(orig) / 2)));
|
|
||||||
building.getBounds().setBounds(building);
|
building.getBounds().setBounds(building);
|
||||||
WorldGrid.updateObject(building, pc);
|
WorldGrid.updateObject(building, pc);
|
||||||
DbManager.BuildingQueries.SET_PROPERTY(building, "rotY", building.getRot().getY());
|
DbManager.BuildingQueries.SET_PROPERTY(building, "rotY", building.getRotation().y);
|
||||||
DbManager.BuildingQueries.SET_PROPERTY(building, "w", building.getw());
|
DbManager.BuildingQueries.SET_PROPERTY(building, "w", building.getRotation().w);
|
||||||
ChatManager.chatSayInfo(pc,
|
ChatManager.chatSayInfo(pc,
|
||||||
"Building with ID " + building.getObjectUUID() + " rotated");
|
"Building with ID " + building.getObjectUUID() + " rotated");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rotateNPC(PlayerCharacter pc, NPC npc, Vector3f rot, boolean faceDirection) {
|
private void rotateNPC(PlayerCharacter pc, NPC npc, float yaw) {
|
||||||
npc.setRot(rot);
|
npc.setRotation(characterRotation(yaw));
|
||||||
DbManager.NPCQueries.SET_PROPERTY(npc, "npc_rotation", rot.y);
|
DbManager.NPCQueries.SET_PROPERTY(npc, "npc_rotation", npc.getRotation().w);
|
||||||
WorldGrid.updateObject(npc, pc);
|
WorldGrid.updateObject(npc, pc);
|
||||||
//no rotation for npc's in db currently
|
|
||||||
ChatManager.chatSayInfo(pc,
|
ChatManager.chatSayInfo(pc,
|
||||||
"NPC with ID " + npc.getObjectUUID() + " rotated");
|
"NPC with ID " + npc.getObjectUUID() + " rotated");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rotateMob(PlayerCharacter pc, Mob mob, Vector3f rot, boolean faceDirection) {
|
private void rotateMob(PlayerCharacter pc, Mob mob, float yaw) {
|
||||||
mob.setRot(rot);
|
mob.setRotation(characterRotation(yaw));
|
||||||
DbManager.MobQueries.SET_PROPERTY(mob, "mob_rotation", rot.y);
|
DbManager.MobQueries.SET_PROPERTY(mob, "mob_rotation", mob.getRotation().w);
|
||||||
WorldGrid.updateObject(mob, pc);
|
WorldGrid.updateObject(mob, pc);
|
||||||
//no rotation for mobs's in db currently
|
|
||||||
ChatManager.chatSayInfo(pc,
|
ChatManager.chatSayInfo(pc,
|
||||||
"Mob with ID " + mob.getObjectUUID() + " rotated");
|
"Mob with ID " + mob.getObjectUUID() + " rotated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// npc_rotation and mob_rotation store w = cos θ/2, which reloads to the same yaw only for θ in [0, 2π).
|
||||||
|
private static Quaternion characterRotation(float yaw) {
|
||||||
|
return Quaternion.fromYaw((float) (yaw - FastMath.TWO_PI * Math.floor(yaw / FastMath.TWO_PI)));
|
||||||
|
}
|
||||||
|
|
||||||
private void rotateFace(PlayerCharacter pc, AbstractGameObject target) {
|
private void rotateFace(PlayerCharacter pc, AbstractGameObject target) {
|
||||||
AbstractWorldObject awo = (AbstractWorldObject) target;
|
AbstractWorldObject awo = (AbstractWorldObject) target;
|
||||||
if (awo == null)
|
if (awo == null)
|
||||||
@@ -230,19 +227,12 @@ public class RotateCmd extends AbstractDevCmd {
|
|||||||
|
|
||||||
float rotangle = faceDirection.getRotation();
|
float rotangle = faceDirection.getRotation();
|
||||||
|
|
||||||
float rot = (float) Math.toDegrees(rotangle);
|
|
||||||
|
|
||||||
if (rot > 180)
|
|
||||||
rot *= -1;
|
|
||||||
|
|
||||||
Vector3f buildingrotation = new Vector3f(0f, rot, 0f);
|
|
||||||
Vector3f rotation = new Vector3f(0f, rotangle, 0f);
|
|
||||||
if (target instanceof Building)
|
if (target instanceof Building)
|
||||||
rotateBuilding(pc, (Building) target, buildingrotation, rot, false);
|
rotateBuilding(pc, (Building) target, rotangle);
|
||||||
else if (target instanceof NPC)
|
else if (target instanceof NPC)
|
||||||
rotateNPC(pc, (NPC) target, rotation, true);
|
rotateNPC(pc, (NPC) target, rotangle);
|
||||||
else if (target instanceof Mob)
|
else if (target instanceof Mob)
|
||||||
rotateMob(pc, (Mob) target, rotation, true);
|
rotateMob(pc, (Mob) target, rotangle);
|
||||||
else
|
else
|
||||||
throwbackError(pc, "Target " + target.getObjectType()
|
throwbackError(pc, "Target " + target.getObjectType()
|
||||||
+ " is not a valid object type");
|
+ " is not a valid object type");
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import java.time.ZoneId;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ public enum BuildingManager {
|
|||||||
public static HashMap<Integer, ConcurrentHashMap<Integer, BuildingFriends>> _buildingFriends = new HashMap<>();
|
public static HashMap<Integer, ConcurrentHashMap<Integer, BuildingFriends>> _buildingFriends = new HashMap<>();
|
||||||
public static HashMap<Integer, ConcurrentHashMap<Integer, Condemned>> _buildingCondemned = new HashMap<>();
|
public static HashMap<Integer, ConcurrentHashMap<Integer, Condemned>> _buildingCondemned = new HashMap<>();
|
||||||
public static HashMap<Integer, ArrayList<Vector3fImmutable>> _buildingPatrolPoints = new HashMap<>();
|
public static HashMap<Integer, ArrayList<Vector3fImmutable>> _buildingPatrolPoints = new HashMap<>();
|
||||||
|
public static HashSet<Integer> _npcBuildingOwners = new HashSet<>();
|
||||||
|
|
||||||
public static int getAvailableSlot(Building building) {
|
public static int getAvailableSlot(Building building) {
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ package engine.gameManager;
|
|||||||
import engine.InterestManagement.WorldGrid;
|
import engine.InterestManagement.WorldGrid;
|
||||||
import engine.loot.WorkOrder;
|
import engine.loot.WorkOrder;
|
||||||
import engine.math.Quaternion;
|
import engine.math.Quaternion;
|
||||||
import engine.math.Vector3f;
|
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.mbEnums;
|
import engine.mbEnums;
|
||||||
import engine.net.Dispatch;
|
import engine.net.Dispatch;
|
||||||
@@ -32,8 +31,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static engine.math.FastMath.acos;
|
|
||||||
|
|
||||||
public enum NPCManager {
|
public enum NPCManager {
|
||||||
|
|
||||||
NPC_MANAGER;
|
NPC_MANAGER;
|
||||||
@@ -227,11 +224,12 @@ public enum NPCManager {
|
|||||||
|
|
||||||
abstractCharacter.loc = new Vector3fImmutable(abstractCharacter.bindLoc);
|
abstractCharacter.loc = new Vector3fImmutable(abstractCharacter.bindLoc);
|
||||||
|
|
||||||
// Rotate NPC rotation by the building's rotation
|
// Rotate NPC rotation by the building's rotation.
|
||||||
|
// Kept for wire parity: the product's sin θ/2 is read back as cos θ/2, so the yaw sent is not own + building.
|
||||||
|
|
||||||
Quaternion slotRotation = new Quaternion().fromAngles(0, acos(abstractCharacter.getRot().y) * 2, 0);
|
Quaternion slotRotation = new Quaternion().fromAngles(0, abstractCharacter.getRotation().getYaw(), 0);
|
||||||
slotRotation = slotRotation.mult(abstractCharacter.building.getBounds().getQuaternion());
|
slotRotation = slotRotation.mult(abstractCharacter.building.getBounds().getQuaternion());
|
||||||
abstractCharacter.setRot(new Vector3f(0, slotRotation.y, 0));
|
abstractCharacter.setRotation(Quaternion.fromYaw((float) Math.acos(slotRotation.y) * 2));
|
||||||
|
|
||||||
// Configure region and floor/level for this NPC
|
// Configure region and floor/level for this NPC
|
||||||
|
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ public class Bounds {
|
|||||||
|
|
||||||
blueprint = building.getBlueprint();
|
blueprint = building.getBlueprint();
|
||||||
|
|
||||||
this.quaternion = new Quaternion(building.getRot().x, building.getRot().y, building.getRot().z, building.getw());
|
this.quaternion = new Quaternion(building.getRotation().x, building.getRotation().y, building.getRotation().z, building.getRotation().w);
|
||||||
|
|
||||||
// Calculate Bounds for non-blueprint objects
|
// Calculate Bounds for non-blueprint objects
|
||||||
|
|
||||||
@@ -437,7 +437,7 @@ public class Bounds {
|
|||||||
|
|
||||||
// The rotation is reset after the new aabb is calculated.
|
// The rotation is reset after the new aabb is calculated.
|
||||||
|
|
||||||
this.rotation = building.getRot().y;
|
this.rotation = building.getRotation().y;
|
||||||
|
|
||||||
// Caclculate and set the new half halfExtents for the rotated bounding box
|
// Caclculate and set the new half halfExtents for the rotated bounding box
|
||||||
// and reset the rotation to 0 for this bounds.
|
// and reset the rotation to 0 for this bounds.
|
||||||
@@ -451,7 +451,7 @@ public class Bounds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.origin.set(building.getLoc().x, building.getLoc().z);
|
this.origin.set(building.getLoc().x, building.getLoc().z);
|
||||||
this.rotation = building.getRot().y;
|
this.rotation = building.getRotation().y;
|
||||||
this.halfExtents.set(blueprint.getExtents());
|
this.halfExtents.set(blueprint.getExtents());
|
||||||
this.flipExtents = Bounds.calculateFlipExtents(this);
|
this.flipExtents = Bounds.calculateFlipExtents(this);
|
||||||
|
|
||||||
|
|||||||
@@ -1142,6 +1142,17 @@ public class Quaternion {
|
|||||||
return opposite(this);
|
return opposite(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client MakeRotateY (Math.dll 0x10002960): w = cos θ/2, y = sin θ/2, radians.
|
||||||
|
// Math rather than FastMath: FastMath.reduceSinAngle mis-reduces angles below -π.
|
||||||
|
public static Quaternion fromYaw(float yaw) {
|
||||||
|
return new Quaternion(0f, (float) Math.sin(yaw * 0.5), 0f, (float) Math.cos(yaw * 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The angle MakeRotateY would need to produce this rotation; characters send it as one float.
|
||||||
|
public float getYaw() {
|
||||||
|
return 2 * FastMath.atan2(y, w);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Quaternion clone() {
|
public Quaternion clone() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||||
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||||
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||||
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||||
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||||
|
// Magicbane Emulator Project © 2013 - 2026
|
||||||
|
// https://www.magicbane.com
|
||||||
|
|
||||||
|
package engine.math;
|
||||||
|
|
||||||
|
// The client's placement (ArcObjectInfo +0x28/+0x34/+0x44, math::Transformation); see MBDATA/specs/WorldPlacement.md §7.
|
||||||
|
|
||||||
|
public class Transform {
|
||||||
|
|
||||||
|
public final Vector3fImmutable location;
|
||||||
|
public final Quaternion rotation;
|
||||||
|
public final Vector3f scale;
|
||||||
|
|
||||||
|
public Transform(Vector3fImmutable location, Quaternion rotation, Vector3f scale) {
|
||||||
|
this.location = location;
|
||||||
|
this.rotation = rotation;
|
||||||
|
this.scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Transform fromYaw(Vector3fImmutable location, float yaw) {
|
||||||
|
return new Transform(location, Quaternion.fromYaw(yaw), new Vector3f(1.0f, 1.0f, 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// world = parent × local, as the client composes zone and object transforms (sb.exe 0x6516C0).
|
||||||
|
public static Transform mult(Transform parent, Transform local) {
|
||||||
|
|
||||||
|
Vector3f scaledLocation;
|
||||||
|
Vector3f rotatedLocation;
|
||||||
|
Quaternion rotation;
|
||||||
|
Vector3f scale;
|
||||||
|
|
||||||
|
scaledLocation = new Vector3f(local.location).mult(parent.scale);
|
||||||
|
rotatedLocation = parent.rotation.mult(scaledLocation);
|
||||||
|
rotation = parent.rotation.mult(local.rotation);
|
||||||
|
scale = parent.scale.mult(local.scale);
|
||||||
|
|
||||||
|
return new Transform(parent.location.add(rotatedLocation), rotation, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1180,10 +1180,10 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
|
|||||||
|
|
||||||
// if W return is negative, this is a -90 rotation not a 90?
|
// if W return is negative, this is a -90 rotation not a 90?
|
||||||
|
|
||||||
newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
|
newMesh = Building.persistBuilding(Building.createBuilding(
|
||||||
currentZone.getObjectUUID(), playerCharacter.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
currentZone, playerCharacter.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||||
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
|
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
|
||||||
completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
|
completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation));
|
||||||
|
|
||||||
// Make sure we have a valid mesh
|
// Make sure we have a valid mesh
|
||||||
|
|
||||||
@@ -1303,10 +1303,10 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
|
|||||||
DateTime completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));
|
DateTime completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));
|
||||||
|
|
||||||
|
|
||||||
newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
|
newMesh = Building.persistBuilding(Building.createBuilding(
|
||||||
currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
currentZone, player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||||
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
|
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
|
||||||
completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
|
completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation));
|
||||||
|
|
||||||
// Make sure we have a valid mesh
|
// Make sure we have a valid mesh
|
||||||
if (newMesh == null) {
|
if (newMesh == null) {
|
||||||
@@ -1342,10 +1342,10 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
|
|||||||
|
|
||||||
City city = City.getCity(currentZone.playerCityUUID);
|
City city = City.getCity(currentZone.playerCityUUID);
|
||||||
|
|
||||||
warehouseBuilding = DbManager.BuildingQueries.CREATE_BUILDING(
|
warehouseBuilding = Building.persistBuilding(Building.createBuilding(
|
||||||
currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
currentZone, player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
|
||||||
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
|
localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
|
||||||
DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
|
DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation));
|
||||||
|
|
||||||
if (warehouseBuilding == null) {
|
if (warehouseBuilding == null) {
|
||||||
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
|
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
|
||||||
@@ -1363,7 +1363,6 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
|
|||||||
MaintenanceManager.setMaintDateTime(warehouseBuilding, LocalDateTime.now().plusDays(7));
|
MaintenanceManager.setMaintDateTime(warehouseBuilding, LocalDateTime.now().plusDays(7));
|
||||||
warehouseBuilding.setLoc(warehouseBuilding.getLoc());
|
warehouseBuilding.setLoc(warehouseBuilding.getLoc());
|
||||||
InterestManager.setObjectDirty(warehouseBuilding);
|
InterestManager.setObjectDirty(warehouseBuilding);
|
||||||
warehouseBuilding.runAfterLoad();
|
|
||||||
|
|
||||||
if (city == null)
|
if (city == null)
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import engine.jobs.PersistentAoeJob;
|
|||||||
import engine.jobs.TrackJob;
|
import engine.jobs.TrackJob;
|
||||||
import engine.math.AtomicFloat;
|
import engine.math.AtomicFloat;
|
||||||
import engine.math.Bounds;
|
import engine.math.Bounds;
|
||||||
|
import engine.math.Quaternion;
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.mbEnums;
|
import engine.mbEnums;
|
||||||
import engine.mbEnums.*;
|
import engine.mbEnums.*;
|
||||||
@@ -536,6 +537,18 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The one yaw float LOADCHARACTER carries; a character in a building gets the building's yaw added.
|
||||||
|
public float getClientYaw() {
|
||||||
|
|
||||||
|
float yaw = this.getRotation().getYaw();
|
||||||
|
|
||||||
|
if (this.building != null)
|
||||||
|
if (this.building.getBounds() != null && this.building.getBounds().getQuaternion() != null)
|
||||||
|
yaw += this.building.getBounds().getQuaternion().angleY;
|
||||||
|
|
||||||
|
return yaw;
|
||||||
|
}
|
||||||
|
|
||||||
public static void teleport(AbstractCharacter abstractCharacter, final Vector3fImmutable targetLoc) {
|
public static void teleport(AbstractCharacter abstractCharacter, final Vector3fImmutable targetLoc) {
|
||||||
abstractCharacter.locationLock.writeLock().lock();
|
abstractCharacter.locationLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import engine.job.JobScheduler;
|
|||||||
import engine.jobs.NoTimeJob;
|
import engine.jobs.NoTimeJob;
|
||||||
import engine.math.AtomicFloat;
|
import engine.math.AtomicFloat;
|
||||||
import engine.math.Bounds;
|
import engine.math.Bounds;
|
||||||
import engine.math.Vector3f;
|
import engine.math.Quaternion;
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.mbEnums.DispatchChannel;
|
import engine.mbEnums.DispatchChannel;
|
||||||
import engine.mbEnums.EffectSourceType;
|
import engine.mbEnums.EffectSourceType;
|
||||||
@@ -56,7 +56,7 @@ public abstract class AbstractWorldObject extends AbstractGameObject {
|
|||||||
protected boolean movingUp = false;
|
protected boolean movingUp = false;
|
||||||
private String name = "";
|
private String name = "";
|
||||||
private byte tier = 0;
|
private byte tier = 0;
|
||||||
private Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f);
|
private Quaternion rotation = new Quaternion();
|
||||||
private int objectTypeMask = 0;
|
private int objectTypeMask = 0;
|
||||||
private Bounds bounds;
|
private Bounds bounds;
|
||||||
|
|
||||||
@@ -528,13 +528,13 @@ public abstract class AbstractWorldObject extends AbstractGameObject {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3f getRot() {
|
public Quaternion getRotation() {
|
||||||
return rot;
|
return rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRot(Vector3f rotation) {
|
public void setRotation(Quaternion rotation) {
|
||||||
synchronized (this.rot) {
|
synchronized (this.rotation) {
|
||||||
this.rot = rotation;
|
this.rotation = rotation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,7 +543,7 @@ public abstract class AbstractWorldObject extends AbstractGameObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setTier(byte tier) {
|
public void setTier(byte tier) {
|
||||||
synchronized (this.rot) {
|
synchronized (this.rotation) {
|
||||||
this.tier = tier;
|
this.tier = tier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,10 +210,10 @@ public final class Bane {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Building stone = DbManager.BuildingQueries.CREATE_BUILDING(
|
Building stone = Building.persistBuilding(Building.createBuilding(
|
||||||
cityZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(),
|
cityZone, player.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(),
|
||||||
localLocation, 1.0f, blueprint.getMaxHealth(rank), ProtectionState.PROTECTED, 0, rank,
|
localLocation, 1.0f, blueprint.getMaxHealth(rank), ProtectionState.PROTECTED, 0, rank,
|
||||||
null, blueprint.getBlueprintUUID(), 1, 0.0f);
|
null, blueprint.getBlueprintUUID(), 1, 0.0f));
|
||||||
|
|
||||||
if (stone == null) {
|
if (stone == null) {
|
||||||
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
|
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import engine.job.JobScheduler;
|
|||||||
import engine.jobs.DoorCloseJob;
|
import engine.jobs.DoorCloseJob;
|
||||||
import engine.jobs.SiegeSpireWithdrawlJob;
|
import engine.jobs.SiegeSpireWithdrawlJob;
|
||||||
import engine.math.Bounds;
|
import engine.math.Bounds;
|
||||||
|
import engine.math.Quaternion;
|
||||||
import engine.math.Vector3f;
|
import engine.math.Vector3f;
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.mbEnums;
|
import engine.mbEnums;
|
||||||
@@ -32,10 +33,12 @@ import engine.net.ByteBufferWriter;
|
|||||||
import engine.net.client.msg.ApplyBuildingEffectMsg;
|
import engine.net.client.msg.ApplyBuildingEffectMsg;
|
||||||
import engine.net.client.msg.UpdateObjectMsg;
|
import engine.net.client.msg.UpdateObjectMsg;
|
||||||
import engine.server.MBServerStatics;
|
import engine.server.MBServerStatics;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.pmw.tinylog.Logger;
|
import org.pmw.tinylog.Logger;
|
||||||
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -83,7 +86,6 @@ public class Building extends AbstractWorldObject {
|
|||||||
public ArrayList<Vector3fImmutable> patrolPoints;
|
public ArrayList<Vector3fImmutable> patrolPoints;
|
||||||
public ProtectionState protectionState = ProtectionState.NONE;
|
public ProtectionState protectionState = ProtectionState.NONE;
|
||||||
protected Resists resists;
|
protected Resists resists;
|
||||||
private float w = 1.0f;
|
|
||||||
private Vector3f meshScale = new Vector3f(1.0f, 1.0f, 1.0f);
|
private Vector3f meshScale = new Vector3f(1.0f, 1.0f, 1.0f);
|
||||||
private int doorState = 0;
|
private int doorState = 0;
|
||||||
private int ownerUUID = 0; //NPC or Character--check ownerIsNPC flag
|
private int ownerUUID = 0; //NPC or Character--check ownerIsNPC flag
|
||||||
@@ -99,6 +101,14 @@ public class Building extends AbstractWorldObject {
|
|||||||
private ConcurrentHashMap<Integer, Condemned> condemned;
|
private ConcurrentHashMap<Integer, Condemned> condemned;
|
||||||
private ArrayList<Building> children = null;
|
private ArrayList<Building> children = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No Id Constructor
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Building() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResultSet Constructor
|
* ResultSet Constructor
|
||||||
*/
|
*/
|
||||||
@@ -118,8 +128,7 @@ public class Building extends AbstractWorldObject {
|
|||||||
this.ownerUUID = rs.getInt("ownerUUID");
|
this.ownerUUID = rs.getInt("ownerUUID");
|
||||||
this.doorState = rs.getInt("doorState");
|
this.doorState = rs.getInt("doorState");
|
||||||
this.setHealth(rs.getInt("currentHP"));
|
this.setHealth(rs.getInt("currentHP"));
|
||||||
this.w = rs.getFloat("w");
|
this.setRotation(new Quaternion(0f, rs.getFloat("rotY"), 0f, rs.getFloat("w")));
|
||||||
this.setRot(new Vector3f(0f, rs.getFloat("rotY"), 0f));
|
|
||||||
this.reverseKOS = rs.getByte("reverseKOS") == 1;
|
this.reverseKOS = rs.getByte("reverseKOS") == 1;
|
||||||
this.statLat = rs.getFloat("locationX");
|
this.statLat = rs.getFloat("locationX");
|
||||||
this.statAlt = rs.getFloat("locationY");
|
this.statAlt = rs.getFloat("locationY");
|
||||||
@@ -173,6 +182,58 @@ public class Building extends AbstractWorldObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Defaults mirror the obj_building column defaults so a new building matches a reloaded one.
|
||||||
|
public static Building createBuilding(Zone parentZone, int ownerUUID, String name, int meshUUID,
|
||||||
|
Vector3fImmutable localLoc, float scale, int currentHP,
|
||||||
|
ProtectionState protectionState, int currentGold, int rank,
|
||||||
|
DateTime upgradeDate, int blueprintUUID, float w, float rotY) {
|
||||||
|
|
||||||
|
Building building = new Building();
|
||||||
|
|
||||||
|
building.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
|
||||||
|
building.gridObjectType = GridObjectType.STATIC;
|
||||||
|
building.parentZoneUUID = parentZone.getObjectUUID();
|
||||||
|
building.ownerUUID = ownerUUID;
|
||||||
|
building.name = name;
|
||||||
|
building.meshUUID = meshUUID;
|
||||||
|
building.blueprintUUID = blueprintUUID;
|
||||||
|
building.statLat = localLoc.x;
|
||||||
|
building.statAlt = localLoc.y;
|
||||||
|
building.statLon = localLoc.z;
|
||||||
|
building.meshScale = new Vector3f(scale, scale, scale);
|
||||||
|
building.setHealth(currentHP);
|
||||||
|
building.protectionState = protectionState;
|
||||||
|
building._strongboxValue = currentGold;
|
||||||
|
building.maxGold = 15000000;
|
||||||
|
building.rank = rank;
|
||||||
|
building.setRotation(new Quaternion(0f, rotY, 0f, w));
|
||||||
|
building.parentBuildingID = 0;
|
||||||
|
building.floor = -1;
|
||||||
|
building.level = -1;
|
||||||
|
|
||||||
|
if (upgradeDate != null)
|
||||||
|
building.upgradeDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(upgradeDate.getMillis()), ZoneId.systemDefault());
|
||||||
|
|
||||||
|
return building;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes a new player building once and loads it into the simulation; returns null if the insert failed.
|
||||||
|
public static Building persistBuilding(Building building) {
|
||||||
|
|
||||||
|
int objectUUID;
|
||||||
|
|
||||||
|
objectUUID = DbManager.BuildingQueries.PERSIST(building);
|
||||||
|
|
||||||
|
if (objectUUID <= 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
building.setObjectUUID(objectUUID);
|
||||||
|
DbManager.addToCache(building);
|
||||||
|
building.runAfterLoad();
|
||||||
|
|
||||||
|
return building;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Getters
|
* Getters
|
||||||
*/
|
*/
|
||||||
@@ -198,11 +259,11 @@ public class Building extends AbstractWorldObject {
|
|||||||
writer.putFloat(building.getLoc().getZ());
|
writer.putFloat(building.getLoc().getZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.putFloat(building.w);
|
writer.putFloat(building.getRotation().w);
|
||||||
writer.putFloat(0f);
|
writer.putFloat(building.getRotation().x);
|
||||||
writer.putFloat(building.getRot().y);
|
writer.putFloat(building.getRotation().y);
|
||||||
|
writer.putFloat(building.getRotation().z);
|
||||||
|
|
||||||
writer.putFloat(0f);
|
|
||||||
writer.putFloat(building.meshScale.getX());
|
writer.putFloat(building.meshScale.getX());
|
||||||
writer.putFloat(building.meshScale.getY());
|
writer.putFloat(building.meshScale.getY());
|
||||||
writer.putFloat(building.meshScale.getZ());
|
writer.putFloat(building.meshScale.getZ());
|
||||||
@@ -689,14 +750,6 @@ public class Building extends AbstractWorldObject {
|
|||||||
this.healthMax = maxHealth;
|
this.healthMax = maxHealth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setw(float value) {
|
|
||||||
this.w = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final float getw() {
|
|
||||||
return this.w;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Vector3f getMeshScale() {
|
public final Vector3f getMeshScale() {
|
||||||
return this.meshScale;
|
return this.meshScale;
|
||||||
}
|
}
|
||||||
@@ -964,15 +1017,10 @@ public class Building extends AbstractWorldObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit upgrade job if building is currently set to rank.
|
// City load reads ownerIsNPC before NPCs are cached, so boot uses the owner set read ahead of buildings.
|
||||||
|
|
||||||
try {
|
this.ownerIsNPC = BuildingManager._npcBuildingOwners.contains(this.ownerUUID)
|
||||||
DbObjectType objectType = DbManager.BuildingQueries.GET_UID_ENUM(this.ownerUUID);
|
|| DbManager.inCache(GameObjectType.NPC, this.ownerUUID);
|
||||||
this.ownerIsNPC = (objectType == DbObjectType.NPC);
|
|
||||||
} catch (Exception e) {
|
|
||||||
this.ownerIsNPC = false;
|
|
||||||
Logger.error("Failed to find Object Type for owner " + this.ownerUUID + " Location " + this.getLoc().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reference friend and condemn lists from BuildingManager
|
// Reference friend and condemn lists from BuildingManager
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import engine.job.JobScheduler;
|
|||||||
import engine.jobs.DeferredPowerJob;
|
import engine.jobs.DeferredPowerJob;
|
||||||
import engine.jobs.UpgradeNPCJob;
|
import engine.jobs.UpgradeNPCJob;
|
||||||
import engine.math.Bounds;
|
import engine.math.Bounds;
|
||||||
|
import engine.math.FastMath;
|
||||||
|
import engine.math.Quaternion;
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.mbEnums;
|
import engine.mbEnums;
|
||||||
import engine.mbEnums.*;
|
import engine.mbEnums.*;
|
||||||
@@ -80,6 +82,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
|||||||
// PERSIST.
|
// PERSIST.
|
||||||
public Mob() {
|
public Mob() {
|
||||||
super();
|
super();
|
||||||
|
this.setRotation(Quaternion.fromYaw(FastMath.PI));
|
||||||
this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
||||||
this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
||||||
this.bindLoc = Vector3fImmutable.ZERO;
|
this.bindLoc = Vector3fImmutable.ZERO;
|
||||||
@@ -99,6 +102,9 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
|||||||
float statAlt;
|
float statAlt;
|
||||||
float statLon;
|
float statLon;
|
||||||
|
|
||||||
|
// mob_rotation is not loaded: the old default (cos θ/2 = 0) faced every mob at yaw π.
|
||||||
|
this.setRotation(Quaternion.fromYaw(FastMath.PI));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.dbID = rs.getInt(1);
|
this.dbID = rs.getInt(1);
|
||||||
this.loadID = rs.getInt("mob_mobbaseID");
|
this.loadID = rs.getInt("mob_mobbaseID");
|
||||||
@@ -240,14 +246,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
|||||||
|
|
||||||
writer.putVector3f(mob.getLoc());
|
writer.putVector3f(mob.getLoc());
|
||||||
|
|
||||||
//Rotation
|
writer.putFloat(mob.getClientYaw());
|
||||||
float radians = (float) Math.acos(mob.getRot().y) * 2;
|
|
||||||
|
|
||||||
if (mob.building != null)
|
|
||||||
if (mob.building.getBounds() != null && mob.building.getBounds().getQuaternion() != null)
|
|
||||||
radians += (mob.building.getBounds().getQuaternion()).angleY;
|
|
||||||
|
|
||||||
writer.putFloat(radians);
|
|
||||||
|
|
||||||
//Inventory Stuff
|
//Inventory Stuff
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import engine.job.JobContainer;
|
|||||||
import engine.job.JobScheduler;
|
import engine.job.JobScheduler;
|
||||||
import engine.jobs.UpgradeNPCJob;
|
import engine.jobs.UpgradeNPCJob;
|
||||||
import engine.math.Bounds;
|
import engine.math.Bounds;
|
||||||
import engine.math.Vector3f;
|
import engine.math.FastMath;
|
||||||
|
import engine.math.Quaternion;
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.mbEnums;
|
import engine.mbEnums;
|
||||||
import engine.mbEnums.*;
|
import engine.mbEnums.*;
|
||||||
@@ -80,6 +81,7 @@ public class NPC extends AbstractCharacter {
|
|||||||
public NPC() {
|
public NPC() {
|
||||||
|
|
||||||
super();
|
super();
|
||||||
|
this.setRotation(Quaternion.fromYaw(FastMath.PI));
|
||||||
this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
||||||
this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
|
||||||
}
|
}
|
||||||
@@ -118,7 +120,8 @@ public class NPC extends AbstractCharacter {
|
|||||||
this.buyPercent = .33f;
|
this.buyPercent = .33f;
|
||||||
this.sellPercent = 1;
|
this.sellPercent = 1;
|
||||||
|
|
||||||
this.setRot(new Vector3f(0, rs.getFloat("npc_rotation"), 0));
|
// npc_rotation holds cos θ/2
|
||||||
|
this.setRotation(Quaternion.fromYaw((float) Math.acos(rs.getFloat("npc_rotation")) * 2));
|
||||||
|
|
||||||
this.statLat = rs.getFloat("npc_spawnX");
|
this.statLat = rs.getFloat("npc_spawnX");
|
||||||
this.statAlt = rs.getFloat("npc_spawnY");
|
this.statAlt = rs.getFloat("npc_spawnY");
|
||||||
@@ -320,14 +323,7 @@ public class NPC extends AbstractCharacter {
|
|||||||
else
|
else
|
||||||
writer.putVector3f(npc.getLoc());
|
writer.putVector3f(npc.getLoc());
|
||||||
|
|
||||||
//Rotation
|
writer.putFloat(npc.getClientYaw());
|
||||||
float radians = (float) Math.acos(npc.getRot().y) * 2;
|
|
||||||
|
|
||||||
if (npc.building != null)
|
|
||||||
if (npc.building.getBounds() != null && npc.building.getBounds().getQuaternion() != null)
|
|
||||||
radians += (npc.building.getBounds().getQuaternion()).angleY;
|
|
||||||
|
|
||||||
writer.putFloat(radians);
|
|
||||||
|
|
||||||
//Running Speed
|
//Running Speed
|
||||||
writer.putInt(0);
|
writer.putInt(0);
|
||||||
|
|||||||
@@ -135,12 +135,12 @@ public class PowerActionParser {
|
|||||||
break;
|
break;
|
||||||
case SetItemFlag:
|
case SetItemFlag:
|
||||||
powerAction.item_flag = header.enumToken(mbEnums.ItemFlags.class, 2);
|
powerAction.item_flag = header.enumToken(mbEnums.ItemFlags.class, 2);
|
||||||
powerAction.item_flag_value = header.optionalBoolToken(3);
|
powerAction.item_flag_value = header.optionalIntToken(3) != 0;
|
||||||
break;
|
break;
|
||||||
case Track:
|
case Track:
|
||||||
powerAction.effect_ids.add(header.token(2));
|
powerAction.effect_ids.add(header.token(2));
|
||||||
powerAction.track_player = header.boolToken(3);
|
powerAction.track_player = header.intToken(3) != 0;
|
||||||
powerAction.track_corpse = header.boolToken(4);
|
powerAction.track_corpse = header.intToken(4) != 0;
|
||||||
powerAction.track_filter = header.token(5);
|
powerAction.track_filter = header.token(5);
|
||||||
powerAction.max_track = header.intToken(7);
|
powerAction.max_track = header.intToken(7);
|
||||||
break;
|
break;
|
||||||
@@ -175,7 +175,7 @@ public class PowerActionParser {
|
|||||||
powerAction.transfer_amount_curve = WpakParserUtil.curveToken(header, 5);
|
powerAction.transfer_amount_curve = WpakParserUtil.curveToken(header, 5);
|
||||||
powerAction.transfer_efficiency = header.floatToken(6);
|
powerAction.transfer_efficiency = header.floatToken(6);
|
||||||
powerAction.transfer_efficiency_curve = WpakParserUtil.curveToken(header, 7);
|
powerAction.transfer_efficiency_curve = WpakParserUtil.curveToken(header, 7);
|
||||||
powerAction.transfer_target_to_caster = header.boolToken(8);
|
powerAction.transfer_target_to_caster = header.intToken(8) != 0;
|
||||||
powerAction.isaggressive = header.optionalIntToken(9) != 0;
|
powerAction.isaggressive = header.optionalIntToken(9) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,13 +186,13 @@ public class PowerActionParser {
|
|||||||
|
|
||||||
switch (line.token(0)) {
|
switch (line.token(0)) {
|
||||||
case "ISAGGRESSIVE=":
|
case "ISAGGRESSIVE=":
|
||||||
powerAction.isaggressive = line.optionalBoolToken(1);
|
powerAction.isaggressive = line.optionalExactTrueToken(1);
|
||||||
break;
|
break;
|
||||||
case "BLADETRAILS=":
|
case "BLADETRAILS=":
|
||||||
powerAction.bladetrails = line.optionalBoolToken(1);
|
powerAction.bladetrails = line.optionalExactTrueToken(1);
|
||||||
break;
|
break;
|
||||||
case "ISRESISTABLE=":
|
case "ISRESISTABLE=":
|
||||||
powerAction.isresistable = line.optionalBoolToken(1);
|
powerAction.isresistable = line.optionalExactTrueToken(1);
|
||||||
break;
|
break;
|
||||||
case "APPLYEFFECTSELF=":
|
case "APPLYEFFECTSELF=":
|
||||||
powerAction.applyeffectself = line.token(1);
|
powerAction.applyeffectself = line.token(1);
|
||||||
@@ -225,10 +225,10 @@ public class PowerActionParser {
|
|||||||
powerAction.femalebodyparts.addAll(line.intTokensFrom(1));
|
powerAction.femalebodyparts.addAll(line.intTokensFrom(1));
|
||||||
break;
|
break;
|
||||||
case "SHOULDSHOWWEAPONS=":
|
case "SHOULDSHOWWEAPONS=":
|
||||||
powerAction.shouldshowweapons = line.optionalBoolToken(1);
|
powerAction.shouldshowweapons = line.optionalExactTrueToken(1);
|
||||||
break;
|
break;
|
||||||
case "SHOULDSHOWARMOR=":
|
case "SHOULDSHOWARMOR=":
|
||||||
powerAction.shouldshowarmor = line.optionalBoolToken(1);
|
powerAction.shouldshowarmor = line.optionalExactTrueToken(1);
|
||||||
break;
|
break;
|
||||||
case "ATTACKANIMS=":
|
case "ATTACKANIMS=":
|
||||||
powerAction.attackanims.addAll(line.intTokensFrom(1));
|
powerAction.attackanims.addAll(line.intTokensFrom(1));
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
public class WpakLine {
|
public class WpakLine {
|
||||||
|
|
||||||
// The client reads numbers like C atoi/atof: the leading number counts, the rest is dropped
|
// The client reads numbers with strtol/strtod (Core.dll String::ToInt/ToFloat): the leading
|
||||||
// (Powers.cfg has "2.9.0" for a float and "10.0" for an int)
|
// number counts, the rest is dropped, so "2.9.0" is 2.9 and "10.0" as an int is 10
|
||||||
private static final Pattern intPrefix = Pattern.compile("[-+]?\\d+");
|
private static final Pattern intPrefix = Pattern.compile("[-+]?\\d+");
|
||||||
private static final Pattern floatPrefix = Pattern.compile("[-+]?(\\d+\\.?\\d*|\\.\\d+)");
|
private static final Pattern floatPrefix = Pattern.compile("[-+]?(\\d+\\.?\\d*|\\.\\d+)");
|
||||||
|
|
||||||
@@ -107,23 +107,44 @@ public class WpakLine {
|
|||||||
return intToken(index);
|
return intToken(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TRUE/FALSE, or a number where non-zero is true
|
// Core.dll String::ToBool with the false default every wpak loader passes: a leading '0' is
|
||||||
|
// false, '1'..'9' is true, then TRUE/FALSE in any case; anything else reads as false
|
||||||
|
|
||||||
public boolean boolToken(int index) {
|
public boolean boolToken(int index) {
|
||||||
|
|
||||||
String value = token(index);
|
String value = token(index);
|
||||||
|
char first = value.isEmpty() ? 0 : value.charAt(0);
|
||||||
|
|
||||||
|
if (first == '0')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (first >= '1' && first <= '9')
|
||||||
|
return true;
|
||||||
|
|
||||||
if (value.equalsIgnoreCase("TRUE"))
|
if (value.equalsIgnoreCase("TRUE"))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (value.equalsIgnoreCase("FALSE"))
|
if (!value.equalsIgnoreCase("FALSE"))
|
||||||
|
Logger.warn(location() + ": token " + index + " '" + value + "' read as false, as the client does");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some PowerActions keys compare the token to "TRUE" exactly (String_EqualsCStr 0x546450), so "1" is false
|
||||||
|
|
||||||
|
public boolean optionalExactTrueToken(int index) {
|
||||||
|
|
||||||
|
String value;
|
||||||
|
|
||||||
|
if (!has(index))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try {
|
value = token(index);
|
||||||
return Integer.parseInt(value) != 0;
|
|
||||||
} catch (NumberFormatException e) {
|
if (!value.equals("TRUE") && !value.equals("FALSE"))
|
||||||
throw error("token " + index + " '" + value + "' is not a boolean");
|
Logger.warn(location() + ": token " + index + " '" + value + "' read as false, as the client does");
|
||||||
}
|
|
||||||
|
return value.equals("TRUE");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean optionalBoolToken(int index) {
|
public boolean optionalBoolToken(int index) {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import engine.powers.RunePowerEntry;
|
|||||||
import engine.powers.RuneSkillAdjustEntry;
|
import engine.powers.RuneSkillAdjustEntry;
|
||||||
import engine.server.MBServerStatics;
|
import engine.server.MBServerStatics;
|
||||||
import engine.util.MapLoader;
|
import engine.util.MapLoader;
|
||||||
|
import engine.util.RotationParityCheck;
|
||||||
import engine.util.ThreadUtils;
|
import engine.util.ThreadUtils;
|
||||||
import engine.workthreads.DisconnectTrashTask;
|
import engine.workthreads.DisconnectTrashTask;
|
||||||
import engine.workthreads.HourlyJobThread;
|
import engine.workthreads.HourlyJobThread;
|
||||||
@@ -529,16 +530,26 @@ public class WorldServer {
|
|||||||
Logger.info("Loading all Zones");
|
Logger.info("Loading all Zones");
|
||||||
DbManager.ZoneQueries.GET_ALL_ZONES();
|
DbManager.ZoneQueries.GET_ALL_ZONES();
|
||||||
Logger.info("Loading all Buildings");
|
Logger.info("Loading all Buildings");
|
||||||
|
BuildingManager._npcBuildingOwners = DbManager.BuildingQueries.GET_NPC_BUILDING_OWNERS();
|
||||||
DbManager.BuildingQueries.GET_ALL_BUILDINGS();
|
DbManager.BuildingQueries.GET_ALL_BUILDINGS();
|
||||||
Logger.info("Loading all Cities");
|
Logger.info("Loading all Cities");
|
||||||
DbManager.CityQueries.GET_ALL_CITIES();
|
DbManager.CityQueries.GET_ALL_CITIES();
|
||||||
Logger.info("Loading all NPCs");
|
Logger.info("Loading all NPCs");
|
||||||
DbManager.NPCQueries.GET_ALL_NPCS();
|
DbManager.NPCQueries.GET_ALL_NPCS();
|
||||||
|
|
||||||
|
// NPCs are cached now; Building.runAfterLoad falls back to the NPC cache.
|
||||||
|
BuildingManager._npcBuildingOwners.clear();
|
||||||
|
|
||||||
Logger.info("Loading all Mobiles");
|
Logger.info("Loading all Mobiles");
|
||||||
DbManager.MobQueries.GET_ALL_MOBS();
|
DbManager.MobQueries.GET_ALL_MOBS();
|
||||||
|
|
||||||
Logger.info("time to load World Objects: " + (System.currentTimeMillis() - start) + " ms");
|
Logger.info("time to load World Objects: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
|
|
||||||
|
if (ParityCheck.isEnabled()) {
|
||||||
|
Logger.info("Running rotation parity check");
|
||||||
|
RotationParityCheck.run();
|
||||||
|
}
|
||||||
|
|
||||||
// Configure realms for serialization
|
// Configure realms for serialization
|
||||||
// Doing this after the world is loaded
|
// Doing this after the world is loaded
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||||
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||||
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||||
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||||
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||||
|
// Magicbane Emulator Project © 2013 - 2026
|
||||||
|
// https://www.magicbane.com
|
||||||
|
|
||||||
|
package engine.util;
|
||||||
|
|
||||||
|
import engine.gameManager.DbManager;
|
||||||
|
import engine.math.FastMath;
|
||||||
|
import engine.math.Quaternion;
|
||||||
|
import engine.mbEnums.GameObjectType;
|
||||||
|
import engine.mbEnums.MobBehaviourType;
|
||||||
|
import engine.objects.*;
|
||||||
|
import org.pmw.tinylog.Logger;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
// Dev-only, World-Boot-Plan 0.4: replays the pre-quaternion rotation code on the DB values and compares its wire
|
||||||
|
// values with the loaded objects. Runs under MB_PARITY_CHECK; delete once a run is clean.
|
||||||
|
|
||||||
|
public class RotationParityCheck {
|
||||||
|
|
||||||
|
private static final float TOLERANCE = 1.0e-5f;
|
||||||
|
private static final int MAX_LOGGED = 20;
|
||||||
|
|
||||||
|
private final String label;
|
||||||
|
private int comparedCount;
|
||||||
|
private int differenceCount;
|
||||||
|
private float maxDifference;
|
||||||
|
|
||||||
|
private RotationParityCheck(String label) {
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void run() {
|
||||||
|
|
||||||
|
HashMap<Integer, float[]> buildingRows;
|
||||||
|
HashMap<Integer, Float> npcRows;
|
||||||
|
|
||||||
|
buildingRows = readBuildingRows();
|
||||||
|
npcRows = readNpcRows();
|
||||||
|
|
||||||
|
checkBuildings(buildingRows);
|
||||||
|
checkNPCs(npcRows);
|
||||||
|
checkMobs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkBuildings(HashMap<Integer, float[]> buildingRows) {
|
||||||
|
|
||||||
|
RotationParityCheck check = new RotationParityCheck("Building");
|
||||||
|
Building building;
|
||||||
|
Quaternion rotation;
|
||||||
|
float[] row;
|
||||||
|
|
||||||
|
for (AbstractGameObject object : DbManager.getList(GameObjectType.Building)) {
|
||||||
|
building = (Building) object;
|
||||||
|
row = buildingRows.get(building.getObjectUUID());
|
||||||
|
|
||||||
|
if (row == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
rotation = building.getRotation();
|
||||||
|
|
||||||
|
// The old serializer wrote (w, 0, rotY, 0).
|
||||||
|
check.compare(building.getObjectUUID(), "w", row[0], rotation.w);
|
||||||
|
check.compare(building.getObjectUUID(), "x", 0f, rotation.x);
|
||||||
|
check.compare(building.getObjectUUID(), "y", row[1], rotation.y);
|
||||||
|
check.compare(building.getObjectUUID(), "z", 0f, rotation.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
check.report();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkNPCs(HashMap<Integer, Float> npcRows) {
|
||||||
|
|
||||||
|
RotationParityCheck check = new RotationParityCheck("NPC");
|
||||||
|
NPC npc;
|
||||||
|
Float halfYawCosine;
|
||||||
|
|
||||||
|
for (AbstractGameObject object : DbManager.getList(GameObjectType.NPC)) {
|
||||||
|
npc = (NPC) object;
|
||||||
|
halfYawCosine = npcRows.get(npc.getObjectUUID());
|
||||||
|
|
||||||
|
if (halfYawCosine == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
check.compare(npc.getObjectUUID(), "yaw", legacyYaw(npc, halfYawCosine, npc.building != null), npc.getClientYaw());
|
||||||
|
}
|
||||||
|
|
||||||
|
check.report();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkMobs() {
|
||||||
|
|
||||||
|
RotationParityCheck check = new RotationParityCheck("Mob");
|
||||||
|
Mob mob;
|
||||||
|
boolean slotted;
|
||||||
|
|
||||||
|
for (AbstractGameObject object : DbManager.getList(GameObjectType.Mob)) {
|
||||||
|
mob = (Mob) object;
|
||||||
|
slotted = mob.building != null && (mob.contract != null || mob.behaviourType == MobBehaviourType.SiegeEngine);
|
||||||
|
|
||||||
|
// Mob never loaded mob_rotation, so the old Vector3f default of 0 was its cos θ/2.
|
||||||
|
check.compare(mob.getObjectUUID(), "yaw", legacyYaw(mob, 0f, slotted), mob.getClientYaw());
|
||||||
|
}
|
||||||
|
|
||||||
|
check.report();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The old slot code, rot.y holder and serializer, from the commit before the quaternion switch.
|
||||||
|
private static float legacyYaw(AbstractCharacter character, float halfYawCosine, boolean slotted) {
|
||||||
|
|
||||||
|
Quaternion buildingRotation = null;
|
||||||
|
Quaternion slotRotation;
|
||||||
|
float yaw;
|
||||||
|
|
||||||
|
if (character.building != null && character.building.getBounds() != null)
|
||||||
|
buildingRotation = character.building.getBounds().getQuaternion();
|
||||||
|
|
||||||
|
if (slotted && buildingRotation != null) {
|
||||||
|
slotRotation = new Quaternion().fromAngles(0, FastMath.acos(halfYawCosine) * 2, 0);
|
||||||
|
slotRotation = slotRotation.mult(buildingRotation);
|
||||||
|
halfYawCosine = slotRotation.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
yaw = (float) Math.acos(halfYawCosine) * 2;
|
||||||
|
|
||||||
|
if (buildingRotation != null)
|
||||||
|
yaw += buildingRotation.angleY;
|
||||||
|
|
||||||
|
return yaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashMap<Integer, float[]> readBuildingRows() {
|
||||||
|
|
||||||
|
HashMap<Integer, float[]> rows = new HashMap<>();
|
||||||
|
|
||||||
|
try (Connection connection = DbManager.getConnection();
|
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `UID`, `w`, `rotY` FROM `obj_building`;")) {
|
||||||
|
|
||||||
|
ResultSet rs = preparedStatement.executeQuery();
|
||||||
|
|
||||||
|
while (rs.next())
|
||||||
|
rows.put(rs.getInt("UID"), new float[]{rs.getFloat("w"), rs.getFloat("rotY")});
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
Logger.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashMap<Integer, Float> readNpcRows() {
|
||||||
|
|
||||||
|
HashMap<Integer, Float> rows = new HashMap<>();
|
||||||
|
|
||||||
|
try (Connection connection = DbManager.getConnection();
|
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `UID`, `npc_rotation` FROM `obj_npc`;")) {
|
||||||
|
|
||||||
|
ResultSet rs = preparedStatement.executeQuery();
|
||||||
|
|
||||||
|
while (rs.next())
|
||||||
|
rows.put(rs.getInt("UID"), rs.getFloat("npc_rotation"));
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
Logger.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angles are compared modulo 2π: the client only feeds them to MakeRotateY.
|
||||||
|
private void compare(int id, String field, float legacyValue, float newValue) {
|
||||||
|
|
||||||
|
float difference;
|
||||||
|
|
||||||
|
comparedCount++;
|
||||||
|
|
||||||
|
if (Float.isNaN(legacyValue) && Float.isNaN(newValue))
|
||||||
|
return;
|
||||||
|
|
||||||
|
difference = Math.abs(newValue - legacyValue);
|
||||||
|
|
||||||
|
if (field.equals("yaw"))
|
||||||
|
difference = Math.abs((float) Math.IEEEremainder(newValue - legacyValue, FastMath.TWO_PI));
|
||||||
|
|
||||||
|
if (Float.isNaN(difference))
|
||||||
|
difference = Float.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
maxDifference = Math.max(maxDifference, difference);
|
||||||
|
|
||||||
|
if (difference <= TOLERANCE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
differenceCount++;
|
||||||
|
|
||||||
|
if (differenceCount <= MAX_LOGGED)
|
||||||
|
Logger.warn("Rotation parity: " + label + "/" + id + " " + field + ": old " + legacyValue + ", new " + newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void report() {
|
||||||
|
Logger.info("Rotation parity: " + label + ": " + comparedCount + " compared, " + differenceCount
|
||||||
|
+ " differences over " + TOLERANCE + ", max difference " + maxDifference);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user