- Joined
- Jul 17, 2010
- Messages
- 333
- Reaction score
- 165
I was just wondering why 'MapSize' and 'MobCapacity' in my server didn't correspond with official server's.
So I decided to investigate what's wrong with my server and have achieved reproducing the official-like mob respawn system.
*Disclaimer*
It doesn't include full source code but pieces of code, and is missing TimeMob, FixedMob and MCMob [STRIKE]but who cares?[/STRIKE]
Special thanks go to authors of MoopleDEV, SwordieMS, and other open sources that I lee...arned from.
1. Get correct 'MapSize' and calculate 'MobCapacity'.
2. Modify MobGen (aka SpawnPoint) and the respawn function.
It's basically based on BMS's leaked files but they still seem to use that formula in up-to-date server.
*Edited*
・modified a formula for 'regenInterval' so that you can easily know that 'mobTime' x1.3 to x2 is the actual respawn interval.
・moved a comment "// set last created time if you're using a field updater" to within a bracket. it should be set only when 'remaingCapacity' > 0.
(which means, if you have a field updater (I mean a repeating timer which updates a field every second instead of 7 seconds), then, when the elapsed time since the capacity is reached exceeds 7 seconds, it will summon mobs within a second after you kill mobs. and I think it's official-like behavior.)
So I decided to investigate what's wrong with my server and have achieved reproducing the official-like mob respawn system.
*Disclaimer*
It doesn't include full source code but pieces of code, and is missing TimeMob, FixedMob and MCMob [STRIKE]but who cares?[/STRIKE]
Special thanks go to authors of MoopleDEV, SwordieMS, and other open sources that I lee...arned from.
1. Get correct 'MapSize' and calculate 'MobCapacity'.
Code:
// ref. CFieldMan::RegisterField (aka FieldFactory#getField, MapleMapFactory#getMap or FieldData#readFieldFromFile or etc...)
// *snip*
// ref. CFieldMan::RestoreFoothold
int mbrLeft = Integer.MAX_VALUE;
int mbrTop = Integer.MAX_VALUE;
int mbrRight = Integer.MIN_VALUE;
int mbrBottom = Integer.MIN_VALUE;
for (/* iterate footholds (from a xml(wz) file or a dat file or etc...) */) {
// make a foothold and add it to the footholds list (omitted)
int left;
int top;
int right;
int bottom;
if (fh.getX1() >= fh.getX2()) {
left = fh.getX2();
right = fh.getX1();
} else {
left = fh.getX1();
right = fh.getX2();
}
if (fh.getY1() >= fh.getY2()) {
top = fh.getY2();
bottom = fh.getY1();
} else {
top = fh.getY1();
bottom = fh.getY2();
}
if (mbrLeft > left + 30) {
mbrLeft = left + 30;
}
if (mbrRight < right - 30) {
mbrRight = right - 30;
}
if (mbrTop > top - 300) {
mbrTop = top - 300;
}
if (mbrBottom < bottom + 10 && [B]left != right[/B]) { // Ignore bottom footholds where you can't walk. Is it not here? but ok.
mbrBottom = bottom + 10;
}
}
if (field.isVrLimit()) { // in rare cases
if (field.getVrLeft() != 0 && mbrLeft < field.getVrLeft() + 20) {
mbrLeft = field.getVrLeft() + 20;
}
if (field.getVrRight() != 0 && mbrRight > field.getVrRight() - 20) {
mbrRight = field.getVrRight() - 20;
}
if (field.getVrTop() != 0 && mbrTop < field.getVrTop() + 65) {
mbrTop = field.getVrTop() + 65;
}
if (field.getVrBottom() != 0 && mbrBottom > field.getVrBottom()) {
mbrBottom = field.getVrBottom();
}
}
Rect mbr = new Rect(mbrLeft, mbrTop, mbrRight, mbrBottom);
mbr.inflate(10, 10); // left - 10, top - 10, right +10, bottom +10
field.setMbr(mbr);
field.setLeftTop(new Point(mbr.getLeft(), mbr.getTop()));
field.setMapSize(new Point(mbr.getWidth(), mbr.getHeight())); // right - left, bottom - top
// *snip*
// ref. CLifePool::Init
int width = Math.max(800, field.getMapSize().getX());
int height = Math.max(600, field.getMapSize().getY() - 450);
int capacity = Math.max(1, Math.min(40, (int) ((height * width) * field.getMobRate() * 0.0000078125)));
field.setMobCapacityMin(capacity);
field.setMobCapacityMax(capacity * 2);
2. Modify MobGen (aka SpawnPoint) and the respawn function.
Code:
// MobGen (aka SpawnPoint or whatever)
// int regenInterval;
// long regenAfter;
// AtomicInteger mobCount = new AtomicInteger(0); // tbh it's replaceable with a boolean
/*
initialize (constructor)
{
// omitted again, sorry. (set templateId and position, fh, etc...)
regenInterval = mobTime; //'mobTime' * 1000
if (regenInterval < 0) {
regenAfter = 0;
} else {
int rAfter;
if (regenInterval == 0) {
rAfter = Randomizer.nextInt(Integer.MAX_VALUE); // whatever
} else {
rAfter = Randomizer.rand(regenInterval / 10, (int) Math.round(regenInterval * 0.7) - 1);
}
regenAfter = rAfter + System.currentTimeMillis();
/* old
int rAfter = 0;
int rand = 6 * regenInterval / 10;
if (rand > 0) {
rAfter = regenInterval / 10 + Randomizer.nextInt(Integer.MAX_VALUE) % rand;
} else {
rAfter = Randomizer.nextInt(Integer.MAX_VALUE);
}
regenAfter = rAfter + System.currentTimeMillis();
*/
}
}
*/
/**
* There are 3 types of MobGens and they are distinguished by their 'mobTime'.
* 0: can summon multiple mobs per a MobGen if there's no mobs around it. (up to MobCapacity)
* x > 0: summon one mob per a MobGen with an interval.
* -1: summon only once when 'reset'.
* [USER=850422]return[/USER] 0: rejected 1: to first position 2: to last position
*/
public int canSpawnMob(long curTime, boolean reset, List<Point> lpt) {
if (regenInterval == 0) {
if (!lpt.isEmpty()) {
Rect rect = new Rect(getPosition().getX() - 100, getPosition().getY() - 100, getPosition().getX() + 100, getPosition().getY() + 100);
for (Point pt : lpt) {
if (rect.isPtInRect(pt)) {
return 0;
}
}
}
return 2;
} else if (regenInterval > 0) {
return mobCount.get() < 1 && regenAfter <= curTime ? 1 : 0;
} else {
return reset ? 1 : 0;
}
}
/**
* ref. CMob::Init
* This function is not needed here but in any case you must assign the MobGen to a mob and increment 'mobCount' when the mob summoned.
*/
public void setMobCreated() {
if (regenInterval != 0) {
mobCount.incrementAndGet();
}
}
/**
* ref. CMob::SetRemoved
* This function is not needed here but in any case you must set 'regenAfter' and decrement 'mobCount' when the summoned mob died.
*/
public void setMobRemoved() {
if (regenInterval != 0) {
if (mobCount.decrementAndGet() == 0) {
int rAfter;
if (regenInterval > 0) {
rAfter = Randomizer.rand((int) Math.round(regenInterval * 1.3), regenInterval * 2 - 1);
} else {
rAfter = Randomizer.nextInt(Integer.MAX_VALUE); // whatever
}
regenAfter = rAfter + System.currentTimeMillis();
/* old
int rAfter;
int rand = 7 * regenInterval / 10;
if (rand != 0) {
rAfter = 13 * regenInterval / 10 + Randomizer.nextInt(Integer.MAX_VALUE) % rand;
} else {
rAfter = Randomizer.nextInt(Integer.MAX_VALUE);
}
regenAfter = rAfter + System.currentTimeMillis();
*/
}
}
}
Code:
// ref. ClifePool::TryCreateMob (aka MapleMap#respawn or Field#generateMob or something)
// *snip*
if (getMobGens().isEmpty()) {
return;
}
long curTime = System.currentTimeMillis();
// check last created time if you're using a field updater.
// eg. if (reset || curTime - lastCreateMobTime >= GameConstants.BASE_MOB_RESPAWN_TIME * (isKishinActivated() ? GameConstants.KISHIN_MOB_TIME_MULTIPLIER : 1)) // 7000 * 0.5 or 1
// oh, maybe I should've wrote it like GameConstants.MOB_RES`PAWN_TIME[getKishinLevel()]
int cap;
if ((getFieldLimit() & FieldOpt.NOMOBCAPACITYLIMIT.getValue()) != 0) {
cap = getMobGens().size();
} else if (getFixedMobCapacity() > 0) {
cap = getFixedMobCapacity();
} else {
int mobCapacityMin = getMobCapacityMin();
int mobCapacityMax = getMobCapacityMax();
int controllerCount = getUsers().size(); // not sure
cap = mobCapacityMin;
if (isKishinActivated()) { // is this here?
cap *= GameConstants.KISHIN_MOB_CAP_MULTIPLIER; // 1.5
}
if (controllerCount > mobCapacityMin / 2) {
cap += (mobCapacityMax - mobCapacityMin) * (2 * controllerCount - mobCapacityMin) / (3 * mobCapacityMin); // not sure
}
cap = Math.min(cap, mobCapacityMax);
}
int remainCapacity = cap - getMobs().size();
if (remainCapacity > 0) {
List<Point> lpt = new ArrayList<>();
for (Mob mob : getMobs()) {
lpt.add(mob.getPosition());
}
LinkedList<MobGen> mobGenPossible = new LinkedList<>();
for (MobGen mobGen : getMobGens()) {
switch (mobGen.canSpawnMob(curTime, reset, lpt)) {
case 1:
mobGenPossible.addFirst(mobGen);
break;
case 2:
mobGenPossible.addLast(mobGen);
break;
default:
continue;
}
lpt.add(mobGen.getPosition());
}
lpt.clear();
do {
if (mobGenPossible.isEmpty()) {
break;
}
int index = 0;
MobGen mobGen = mobGenPossible.getFirst();
if (mobGen.getRegenInterval() == 0 && mobGenPossible.size() > 1) {
index = Randomizer.nextInt(Integer.MAX_VALUE) % mobGenPossible.size();
mobGen = mobGenPossible.get(index);
}
if (createMob(mobGen.getTemplateId(), mobGen, mobGen.getPosition().getX(), mobGen.getPosition().getY(), mobGen.getFh(), false, -2, 0, mobGen.getF(), 0))) {
remainCapacity--;
}
mobGenPossible.remove(index);
} while (remainCapacity > 0);
mobGenPossible.clear();
// set last created time if you're using a field updater
}
*Edited*
・modified a formula for 'regenInterval' so that you can easily know that 'mobTime' x1.3 to x2 is the actual respawn interval.
・moved a comment "// set last created time if you're using a field updater" to within a bracket. it should be set only when 'remaingCapacity' > 0.
(which means, if you have a field updater (I mean a repeating timer which updates a field every second instead of 7 seconds), then, when the elapsed time since the capacity is reached exceeds 7 seconds, it will summon mobs within a second after you kill mobs. and I think it's official-like behavior.)
Last edited: