forked from MagicBane/Server
Derive mob attributes from every rune the client is sent
Client ArcCharacter_RecalcMaxAttributes (0x470440) and RecalcAttributes (0x470610) treat the stats in the mob message as spent points and add rune_attr_adj from all of the character's runes, clamped to [1, max(1, sum of rune_max_attr_adj)]. The server summed only the race rune and sent the result, so the client counted it twice. Mob.getClientRuneIDs lists the runes serializeForClientMsgOtherPlayer sends (race, plus Pet 2522 for pets, Warrior 2518 + Guard 252621 for contract and guard mobs); getRuneAttribute applies the client rule. calculateModifiedStats and the mob branch of CharacterSkill use it, and the message now sends 0 (no spent points), as NPCs already do. MobBaseStats (race rune only) has no users left and is removed. Value changes (server side): - Contract mobs and player guards: +100 to all five attributes (Guard rune 252621; its max adj is also +100, so no new clamp). - Pets: + Pet rune 2522 (Str 76, Dex 76, Con 120, Int 40, Spi 40; max Str 500, Dex 500, Con 2000, Int 40, Spi 40). - The new max clamp changes no loaded mobbase on its own (checked all 2375 against their race runes). These feed mob skill values (CharacterSkill) and statXxxCurrent. Guard ATR/defense/damage/health still come from the NPCManager guard formulas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c58586f89
commit
b0460ed2d0
4 files changed
+55
-93
No files matched your search
+50
-11
@@ -22,6 +22,7 @@ import engine.net.ByteBufferWriter;
|
||||
import engine.net.Dispatch;
|
||||
import engine.net.client.msg.PetMsg;
|
||||
import engine.net.client.msg.PlaceAssetMsg;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
import engine.server.MBServerStatics;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -29,6 +30,7 @@ import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Delayed;
|
||||
@@ -198,19 +200,19 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
writer.putInt(mob.getObjectType().ordinal());
|
||||
writer.putInt(mob.currentID);
|
||||
|
||||
//Send Stats
|
||||
// Stats are the base (spent points, 0 for mobs); the client adds every sent rune's attr adj (0x470610)
|
||||
|
||||
writer.putInt(5);
|
||||
writer.putInt(MBServerStatics.STAT_STR_ID);
|
||||
writer.putInt(mob.statStrCurrent);
|
||||
writer.putInt(0);
|
||||
writer.putInt(MBServerStatics.STAT_DEX_ID);
|
||||
writer.putInt(mob.statDexCurrent);
|
||||
writer.putInt(0);
|
||||
writer.putInt(MBServerStatics.STAT_CON_ID);
|
||||
writer.putInt(mob.statConCurrent);
|
||||
writer.putInt(0);
|
||||
writer.putInt(MBServerStatics.STAT_INT_ID);
|
||||
writer.putInt(mob.statIntCurrent);
|
||||
writer.putInt(0);
|
||||
writer.putInt(MBServerStatics.STAT_SPI_ID);
|
||||
writer.putInt(mob.statSpiCurrent);
|
||||
writer.putInt(0);
|
||||
|
||||
|
||||
writer.putString(mob.firstName);
|
||||
@@ -725,13 +727,50 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
return this.bindLoc;
|
||||
}
|
||||
|
||||
// The runes serializeForClientMsgOtherPlayer sends; the client derives the mob's attributes from all of them
|
||||
public ArrayList<Integer> getClientRuneIDs() {
|
||||
|
||||
ArrayList<Integer> runeIDs = new ArrayList<>();
|
||||
|
||||
if (this.isPet())
|
||||
runeIDs.add(2522);
|
||||
else if (this.contract != null || this.isPlayerGuard()) {
|
||||
runeIDs.add(2518);
|
||||
runeIDs.add(252621);
|
||||
}
|
||||
|
||||
runeIDs.add((this.mobBase != null) ? this.mobBase.getObjectUUID() : this.loadID);
|
||||
return runeIDs;
|
||||
}
|
||||
|
||||
// Client ArcCharacter_RecalcMaxAttributes 0x470440 + RecalcAttributes 0x470610 with no spent points
|
||||
public int getRuneAttribute(AttributeType attributeType) {
|
||||
|
||||
RuneTemplate rune;
|
||||
int value = 0;
|
||||
int max = 0;
|
||||
|
||||
for (int runeID : this.getClientRuneIDs()) {
|
||||
rune = RuneTemplate._runes.get(runeID);
|
||||
|
||||
if (rune == null)
|
||||
continue;
|
||||
|
||||
value += rune.rune_attr_adj.getOrDefault(attributeType, 0);
|
||||
max += rune.rune_max_attr_adj.getOrDefault(attributeType, 0);
|
||||
}
|
||||
|
||||
max = Math.max(max, 1);
|
||||
return Math.min(Math.max(value, 1), max);
|
||||
}
|
||||
|
||||
public void calculateModifiedStats() {
|
||||
|
||||
float strVal = this.mobBase.getMobBaseStats().getBaseStr();
|
||||
float dexVal = this.mobBase.getMobBaseStats().getBaseDex();
|
||||
float conVal = this.mobBase.getMobBaseStats().getBaseCon();
|
||||
float intVal = this.mobBase.getMobBaseStats().getBaseInt();
|
||||
float spiVal = this.mobBase.getMobBaseStats().getBaseSpi();
|
||||
float strVal = this.getRuneAttribute(AttributeType.Strength);
|
||||
float dexVal = this.getRuneAttribute(AttributeType.Dexterity);
|
||||
float conVal = this.getRuneAttribute(AttributeType.Constitution);
|
||||
float intVal = this.getRuneAttribute(AttributeType.Intelligence);
|
||||
float spiVal = this.getRuneAttribute(AttributeType.Spirit);
|
||||
|
||||
// TODO modify for equipment
|
||||
if (this.bonuses != null) {
|
||||
|
||||
Reference in new issue
Block a user