Build static mobs from zone mobiles with their CZONE runes, equipment and booty
Mob.createStaticMob fills one member of a MOBILE zone mobile's group: race rune as mobbase, name, mobile_level (kept over the mobbase level when set), respawn, radius, root FSM, enemy types and π − y_rot facing. Static mobs send the CZONE rune list ordered by kind, wear mobile_equipment and roll mobile_booties as ITEM booty. Other mobs keep the server-picked runes and equipment sets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
820177529d
commit
0fdc96d9f2
4 files changed
+125
-39
No files matched your search
@@ -42,6 +42,14 @@ public class SimulateBootyCmd extends AbstractDevCmd {
|
||||
}
|
||||
}
|
||||
|
||||
for (BootySetEntry entry : mob.staticBooty) {
|
||||
|
||||
CObjectTemplate template = CObjectTemplate.lookup(entry.templateID);
|
||||
|
||||
if (template != null)
|
||||
output += "[" + entry.bootyType + "] " + template.getItemBaseName() + " [Chance] " + entry.dropChance + newline;
|
||||
}
|
||||
|
||||
ArrayList<Item> GlassItems = new ArrayList<Item>();
|
||||
ArrayList<Item> Resources = new ArrayList<Item>();
|
||||
ArrayList<Item> Runes = new ArrayList<Item>();
|
||||
|
||||
@@ -89,6 +89,9 @@ public enum LootManager {
|
||||
if (mob.bootySet != 0 && _bootySetMap.containsKey(mob.bootySet) == true)
|
||||
RunBootySet(_bootySetMap.get(mob.bootySet), mob, inHotzone);
|
||||
|
||||
if (!mob.staticBooty.isEmpty())
|
||||
RunBootySet(mob.staticBooty, mob, inHotzone);
|
||||
|
||||
//lastly, check mobs inventory for godly or disc runes to send a server announcement
|
||||
for (Item item : mob.getInventory()) {
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
package engine.loot;
|
||||
|
||||
import engine.parsers.objects.CZoneDrop;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@@ -34,4 +36,11 @@ public class BootySetEntry {
|
||||
this.dropChance = (rs.getFloat("dropChance"));
|
||||
}
|
||||
|
||||
// An ITEM entry from a zone mobile's mobile_booties (World Boot Plan 3.3); the chance is a percent, as in the DB
|
||||
public BootySetEntry(CZoneDrop booty) {
|
||||
this.bootyType = "ITEM";
|
||||
this.templateID = booty.item_id;
|
||||
this.dropChance = booty.chance;
|
||||
}
|
||||
|
||||
}
|
||||
+105
-39
@@ -14,6 +14,7 @@ import engine.gameManager.*;
|
||||
import engine.job.JobScheduler;
|
||||
import engine.jobs.DeferredPowerJob;
|
||||
import engine.jobs.UpgradeNPCJob;
|
||||
import engine.loot.BootySetEntry;
|
||||
import engine.math.Bounds;
|
||||
import engine.math.FastMath;
|
||||
import engine.math.Quaternion;
|
||||
@@ -24,6 +25,9 @@ import engine.net.ByteBufferWriter;
|
||||
import engine.net.Dispatch;
|
||||
import engine.net.client.msg.PetMsg;
|
||||
import engine.net.client.msg.PlaceAssetMsg;
|
||||
import engine.parsers.objects.CZoneDrop;
|
||||
import engine.parsers.objects.CZoneMobile;
|
||||
import engine.parsers.objects.CZonePlacement;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
import engine.server.MBServerStatics;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -76,6 +80,9 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
private DeferredPowerJob weaponPower;
|
||||
private DateTime upgradeDateTime = null;
|
||||
private boolean lootSync = false;
|
||||
// Static zone mobiles (World Boot Plan 3.3): worn equipment and item booty from the CZONE entry
|
||||
private ArrayList<CZoneDrop> staticEquipment = null;
|
||||
public final ArrayList<BootySetEntry> staticBooty = new ArrayList<>();
|
||||
|
||||
// New Mobile constructor. Fill in the blanks and then call
|
||||
// PERSIST.
|
||||
@@ -171,39 +178,10 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
writer.putInt(0);
|
||||
writer.putInt(0);
|
||||
|
||||
int tid = (mob.mobBase != null) ? mob.mobBase.getObjectUUID() : 0;
|
||||
if (mob.isPet()) {
|
||||
writer.putInt(2);
|
||||
writer.putInt(3);
|
||||
writer.putInt(0);
|
||||
writer.putInt(2522);
|
||||
writer.putInt(GameObjectType.NPCClassRune.ordinal());
|
||||
writer.putInt(mob.currentID);
|
||||
} else if (tid == 100570) { //kur'adar
|
||||
writer.putInt(3);
|
||||
Mob.serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), 2518); //warrior class
|
||||
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune
|
||||
} else if (tid == 100962 || tid == 100965) { //Spydraxxx the Mighty, Denigo Tantric
|
||||
writer.putInt(2);
|
||||
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneTwo.ordinal(), 252621); //guard rune
|
||||
} else if (mob.contract != null || mob.isPlayerGuard()) {
|
||||
writer.putInt(3);
|
||||
serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), 2518); //warrior class
|
||||
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), mob.getGuardRuneID());
|
||||
} else
|
||||
writer.putInt(1);
|
||||
|
||||
//Generate Race Rune
|
||||
writer.putInt(1);
|
||||
writer.putInt(0);
|
||||
|
||||
if (mob.mobBase != null)
|
||||
writer.putInt(mob.mobBase.getObjectUUID());
|
||||
if (mob.staticRuneIDs.isEmpty())
|
||||
serializeServerRunes(mob, writer);
|
||||
else
|
||||
writer.putInt(mob.loadID);
|
||||
|
||||
writer.putInt(mob.getObjectType().ordinal());
|
||||
writer.putInt(mob.currentID);
|
||||
serializeStaticRunes(mob, mob.getObjectType(), mob.currentID, writer);
|
||||
|
||||
// Stats are the base (spent points, 0 for mobs); the client adds every sent rune's attr adj (0x470610)
|
||||
|
||||
@@ -390,6 +368,45 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
writer.put((byte) 0);
|
||||
}
|
||||
|
||||
// Mobs with no zone entry (pets, guards, minions, siege) send runes the server picks
|
||||
private static void serializeServerRunes(Mob mob, ByteBufferWriter writer) {
|
||||
|
||||
int tid = (mob.mobBase != null) ? mob.mobBase.getObjectUUID() : 0;
|
||||
|
||||
if (mob.isPet()) {
|
||||
writer.putInt(2);
|
||||
writer.putInt(3);
|
||||
writer.putInt(0);
|
||||
writer.putInt(2522);
|
||||
writer.putInt(GameObjectType.NPCClassRune.ordinal());
|
||||
writer.putInt(mob.currentID);
|
||||
} else if (tid == 100570) { //kur'adar
|
||||
writer.putInt(3);
|
||||
Mob.serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), 2518); //warrior class
|
||||
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune
|
||||
} else if (tid == 100962 || tid == 100965) { //Spydraxxx the Mighty, Denigo Tantric
|
||||
writer.putInt(2);
|
||||
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneTwo.ordinal(), 252621); //guard rune
|
||||
} else if (mob.contract != null || mob.isPlayerGuard()) {
|
||||
writer.putInt(3);
|
||||
serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), 2518); //warrior class
|
||||
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), mob.getGuardRuneID());
|
||||
} else
|
||||
writer.putInt(1);
|
||||
|
||||
//Generate Race Rune
|
||||
writer.putInt(1);
|
||||
writer.putInt(0);
|
||||
|
||||
if (mob.mobBase != null)
|
||||
writer.putInt(mob.mobBase.getObjectUUID());
|
||||
else
|
||||
writer.putInt(mob.loadID);
|
||||
|
||||
writer.putInt(mob.getObjectType().ordinal());
|
||||
writer.putInt(mob.currentID);
|
||||
}
|
||||
|
||||
private static void serializeRune(Mob mob, ByteBufferWriter writer, int type, int objectType, int runeID) {
|
||||
writer.putInt(type);
|
||||
writer.putInt(0);
|
||||
@@ -436,6 +453,39 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
return mobile;
|
||||
}
|
||||
|
||||
// World Boot Plan 3.3: one member of a MOBILE zone mobile's group. The caller adds it to the cache and runs runAfterLoad.
|
||||
public static Mob createStaticMob(int mobID, Zone parentZone, CZoneMobile zoneMobile, Building building) {
|
||||
|
||||
Mob mobile = new Mob();
|
||||
CZonePlacement placement = zoneMobile.mobile_base_zone;
|
||||
|
||||
mobile.setObjectUUID(mobID);
|
||||
mobile.currentID = mobID;
|
||||
mobile.setStaticWorldObject(true);
|
||||
mobile.loadID = zoneMobile.mobile_rune_stone_ids.get(0);
|
||||
mobile.level = (short) zoneMobile.mobile_level;
|
||||
mobile.parentZoneUUID = parentZone.getObjectUUID();
|
||||
mobile.buildingUUID = building == null ? 0 : building.getObjectUUID();
|
||||
mobile.bindLoc = placement.basic_zone_spawn_location;
|
||||
mobile.spawnRadius = placement.basic_zone_spawn_radius;
|
||||
mobile.spawnDelay = (int) placement.basic_zone_time_to_respawn;
|
||||
mobile.firstName = placement.basic_zone_name_override;
|
||||
mobile.behaviourType = zoneMobile.mobile_root_fs_mid == null ? MobBehaviourType.None : zoneMobile.mobile_root_fs_mid;
|
||||
mobile.enemy.addAll(zoneMobile.mobile_enemy_monster_types);
|
||||
mobile.notEnemy.addAll(zoneMobile.mobile_not_enemy_monster_types);
|
||||
|
||||
// W8: zone mobiles face π − y_rot, like props
|
||||
mobile.setRotation(Quaternion.fromYaw((float) (Math.PI - placement.basic_zone_y_rot)));
|
||||
|
||||
mobile.staticRuneIDs.addAll(zoneMobile.mobile_rune_stone_ids);
|
||||
mobile.staticEquipment = zoneMobile.mobile_equipment;
|
||||
|
||||
for (CZoneDrop booty : zoneMobile.mobile_booties)
|
||||
mobile.staticBooty.add(new BootySetEntry(booty));
|
||||
|
||||
return mobile;
|
||||
}
|
||||
|
||||
// MB_MINION_SPAWN_TIME (seconds) shortens guard/siege minion spawns for testing; 0 or blank keeps the normal timers.
|
||||
private static int minionSpawnDelay(int defaultDelay) {
|
||||
|
||||
@@ -730,6 +780,9 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
|
||||
ArrayList<Integer> runeIDs = new ArrayList<>();
|
||||
|
||||
if (!this.staticRuneIDs.isEmpty())
|
||||
return this.getStaticRunesByKind();
|
||||
|
||||
if (this.isPet())
|
||||
runeIDs.add(2522);
|
||||
else if (this.contract != null || this.isPlayerGuard()) {
|
||||
@@ -1089,7 +1142,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
|
||||
// Reload equipment set
|
||||
|
||||
this.charItemManager.equipped = MobBase.loadEquipmentSet(this.equipmentSetID);
|
||||
this.charItemManager.equipped = this.loadEquipment();
|
||||
|
||||
// Only generate loot for mobiles
|
||||
|
||||
@@ -1099,6 +1152,15 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
LootManager.GenerateMobLoot(this);
|
||||
}
|
||||
|
||||
// Static zone mobiles wear only their CZONE equipment
|
||||
private ConcurrentHashMap<EquipSlotType, Item> loadEquipment() {
|
||||
|
||||
if (this.staticEquipment != null)
|
||||
return MobBase.loadEquipment(this.staticEquipment);
|
||||
|
||||
return MobBase.loadEquipmentSet(this.equipmentSetID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDatabase() {
|
||||
}
|
||||
@@ -1188,6 +1250,8 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
@Override
|
||||
public void runAfterLoad() {
|
||||
|
||||
boolean keepsLevel;
|
||||
|
||||
this.setObjectTypeMask(MBServerStatics.MASK_MOB | this.getTypeMasks());
|
||||
|
||||
if (ConfigManager.serverType.equals(ServerType.LOGINSERVER))
|
||||
@@ -1258,11 +1322,13 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
this.mana.set(this.manaMax);
|
||||
this.stamina.set(this.staminaMax);
|
||||
|
||||
// Don't override level for guard minions or pets
|
||||
// Don't override level for guard minions or pets, or a zone mobile's mobile_level (World Boot Plan findings §6, 3.1)
|
||||
|
||||
if (this.contract == null)
|
||||
if (!this.agentType.equals(AIAgentType.GUARDMINION) && !this.agentType.equals(AIAgentType.PET))
|
||||
this.level = (short) this.mobBase.getLevel();
|
||||
keepsLevel = this.agentType.equals(AIAgentType.GUARDMINION) || this.agentType.equals(AIAgentType.PET)
|
||||
|| (this.isStaticWorldObject() && this.level > 0);
|
||||
|
||||
if (this.contract == null && !keepsLevel)
|
||||
this.level = (short) this.mobBase.getLevel();
|
||||
|
||||
//set bonuses
|
||||
|
||||
@@ -1316,8 +1382,8 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
this.charItemManager.load();
|
||||
this.loadInventory();
|
||||
|
||||
if (this.equipmentSetID != 0)
|
||||
this.charItemManager.equipped = MobBase.loadEquipmentSet(this.equipmentSetID);
|
||||
if (this.equipmentSetID != 0 || this.staticEquipment != null)
|
||||
this.charItemManager.equipped = this.loadEquipment();
|
||||
|
||||
// Combine mobbase and mob aggro arrays into one bitvector
|
||||
//skip for pets
|
||||
|
||||
Reference in new issue
Block a user