Implemented player tick (health/nano stamina).

* The player now heals while not in combat
* Nanos lose stamina while active, regain it while resting
* Using active nano powers drains stamina
* Standing in FM patches/lakes now deals damage
* Fixed a memory error in npcAttackPc()
* Mobs now aggro when a player gets close
* Mobs now give up the chase if the player gets out of the combat zone;
they no longer try chasing until they themselves have left it
* Added a few missing break statements in the loops in BuddyManager

Other players are not yet instantly notified of health/stamina updates,
as finding the correct way to do this has proven tricky. FM patch damage
updates other player's views just fine, though.
This commit is contained in:
dongresource 2020-09-27 00:16:15 +02:00
parent 43d268e142
commit 4fa6618abb
6 changed files with 154 additions and 9 deletions

View File

@ -38,6 +38,7 @@ void BuddyManager::requestBuddy(CNSocket* sock, CNPacketData* data) {
for (auto pair : PlayerManager::players) {
if (pair.second.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
otherSock = pair.first;
break;
}
}
@ -73,6 +74,7 @@ void BuddyManager::reqBuddyByName(CNSocket* sock, CNPacketData* data) {
int sizeOfLNReq = sizeof(pair.second.plr->PCStyle.szLastName) / 17;
if (BuddyManager::firstNameCheck(pair.second.plr->PCStyle.szFirstName, pkt->szFirstName, sizeOfReq, sizeOfRes) == true && BuddyManager::lastNameCheck(pair.second.plr->PCStyle.szLastName, pkt->szLastName, sizeOfLNReq, sizeOfLNRes) == true) { //This long line of gorgeous parameters is to check if the player's name matches :eyes:
otherSock = pair.first;
break;
}
}
@ -100,6 +102,7 @@ void BuddyManager::reqAcceptBuddy(CNSocket* sock, CNPacketData* data) {
for (auto pair : PlayerManager::players) {
if (pair.second.plr->PCStyle.iPC_UID == pkt->iBuddyPCUID) {
otherSock = pair.first;
break;
}
}
@ -166,6 +169,7 @@ void BuddyManager::reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) {
int sizeOfLNReq = sizeof(pair.second.plr->PCStyle.szLastName) / 17;
if (BuddyManager::firstNameCheck(pair.second.plr->PCStyle.szFirstName, pkt->szFirstName, sizeOfReq, sizeOfRes) == true && BuddyManager::lastNameCheck(pair.second.plr->PCStyle.szLastName, pkt->szLastName, sizeOfLNReq, sizeOfLNRes) == true) {
otherSock = pair.first;
break;
}
}

View File

@ -427,6 +427,9 @@ Player Database::DbToPlayer(DbPlayer player) {
result.equippedNanos[1] = player.Nano2;
result.equippedNanos[2] = player.Nano3;
result.dotDamage = false;
result.inCombat = false;
result.iWarpLocationFlag = player.WarpLocationFlag;
result.aSkywayLocationFlag[0] = player.SkywayLocationFlag1;
result.aSkywayLocationFlag[1] = player.SkywayLocationFlag2;

View File

@ -11,6 +11,7 @@ std::map<int32_t, Mob*> MobManager::Mobs;
void MobManager::init() {
REGISTER_SHARD_TIMER(step, 200);
REGISTER_SHARD_TIMER(playerTick, 2000);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs);
@ -103,7 +104,7 @@ void MobManager::npcAttackPc(Mob *mob) {
atk->iHitFlag = 2;
mob->target->sendPacket((void*)respbuf, P_FE2CL_NPC_ATTACK_PCs, resplen);
PlayerManager::sendToViewable(mob->target, (void*)&pkt, P_FE2CL_NPC_ATTACK_PCs, resplen);
PlayerManager::sendToViewable(mob->target, (void*)respbuf, P_FE2CL_NPC_ATTACK_PCs, resplen);
if (plr->HP <= 0) {
mob->target = nullptr;
@ -111,10 +112,6 @@ void MobManager::npcAttackPc(Mob *mob) {
}
}
void MobManager::combatBegin(CNSocket *sock, CNPacketData *data) {} // stub
void MobManager::combatEnd(CNSocket *sock, CNPacketData *data) {} // stub
void MobManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {} // stub
void MobManager::giveReward(CNSocket *sock) {
Player *plr = PlayerManager::getPlayer(sock);
@ -176,6 +173,7 @@ int MobManager::hitMob(CNSocket *sock, Mob *mob, int damage) {
mob->target = sock;
mob->state = MobState::COMBAT;
mob->nextMovement = getTime();
mob->nextAttack = 0;
}
mob->appearanceData.iHP -= damage;
@ -298,8 +296,8 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
}
// retreat if kited too far
distance = hypot(mob->appearanceData.iX - mob->spawnX, mob->appearanceData.iY - mob->spawnY);
// retreat if the player leaves combat range
distance = hypot(plr->x - mob->spawnX, plr->y - mob->spawnY);
if (distance >= mob->data["m_iCombatRange"]) {
mob->target = nullptr;
mob->state = MobState::RETREAT;
@ -313,6 +311,35 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
* Bad Max, etc.) once those have been made.
*/
void MobManager::roamingStep(Mob *mob, time_t currTime) {
/*
* We reuse nextAttack to avoid scanning for players all the time, but to still
* do so more often than if we waited for nextMovement (which is way too slow).
*/
if (mob->nextAttack == 0 || currTime < mob->nextAttack) {
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
/*
* Aggro on nearby players.
* Even if they're in range, we can't assume they're all in the same one chunk
* as the mob, since it might be near a chunk boundary.
*/
for (Chunk *chunk : mob->currentChunks) {
for (CNSocket *s : chunk->players) {
Player *plr = s->plr;
int distance = hypot(mob->appearanceData.iX - plr->x, mob->appearanceData.iY - plr->y);
if (distance > mob->data["m_iSightRange"])
continue;
// found player. engage.
mob->target = s;
mob->state = MobState::COMBAT;
mob->nextMovement = currTime;
mob->nextAttack = 0;
return;
}
}
}
// some mobs don't move (and we mustn't divide/modulus by zero)
if (mob->idleRange == 0)
return;
@ -433,3 +460,107 @@ std::pair<int,int> MobManager::lerp(int x1, int y1, int x2, int y2, int speed) {
return ret;
}
void MobManager::combatBegin(CNSocket *sock, CNPacketData *data) {
Player *plr = PlayerManager::getPlayer(sock);
plr->inCombat = true;
}
void MobManager::combatEnd(CNSocket *sock, CNPacketData *data) {
Player *plr = PlayerManager::getPlayer(sock);
plr->inCombat = false;
}
void MobManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
plr->dotDamage = (bool)pkt->iFlag;
}
void MobManager::dealGooDamage(CNSocket *sock, int amount) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[resplen];
memset(respbuf, 0, resplen);
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
Player *plr = PlayerManager::getPlayer(sock);
// update player
plr->HP -= amount;
pkt->iID = plr->iID;
pkt->eCT = 1; // player
pkt->iTB_ID = ECSB_INFECTION; // sSkillResult_DotDamage
dmg->eCT = 1;
dmg->iID = plr->iID;
dmg->iDamage = amount;
dmg->iHP = plr->HP;
sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
}
void MobManager::playerTick(CNServer *serv, time_t currTime) {
static time_t lastHealTime = 0;
for (auto& pair : PlayerManager::players) {
CNSocket *sock = pair.first;
Player *plr = pair.second.plr;
bool transmit = false;
// fm patch/lake damage
if (plr->dotDamage)
dealGooDamage(sock, 150);
// a somewhat hacky way tick goo damage faster than heal, but eh
if (currTime - lastHealTime < 4000)
continue;
lastHealTime = currTime;
// heal
if (!plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
plr->HP += 200;
if (plr->HP > PC_MAXHEALTH(plr->level))
plr->HP = PC_MAXHEALTH(plr->level);
transmit = true;
}
for (int i = 0; i < 3; i++) {
if (plr->activeNano != 0 && plr->equippedNanos[i] == plr->activeNano) { // spend stamina
plr->Nanos[plr->activeNano].iStamina -= 3;
if (plr->Nanos[plr->activeNano].iStamina < 0)
plr->activeNano = 0;
transmit = true;
} else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina
sNano& nano = plr->Nanos[plr->equippedNanos[i]];
nano.iStamina += 3;
if (nano.iStamina > 150)
nano.iStamina = 150;
transmit = true;
}
}
if (transmit) {
INITSTRUCT(sP_FE2CL_REP_PC_TICK, pkt);
std::cout << "sending sP_FE2CL_REP_PC_TICK" << std::endl;
pkt.iHP = plr->HP;
pkt.iBatteryN = plr->batteryN;
pkt.aNano[0] = plr->Nanos[plr->equippedNanos[0]];
pkt.aNano[1] = plr->Nanos[plr->equippedNanos[1]];
pkt.aNano[2] = plr->Nanos[plr->equippedNanos[2]];
sock->sendPacket((void*)&pkt, P_FE2CL_REP_PC_TICK, sizeof(sP_FE2CL_REP_PC_TICK));
}
}
}

View File

@ -86,6 +86,7 @@ namespace MobManager {
void init();
void step(CNServer*, time_t);
void playerTick(CNServer*, time_t);
void deadStep(Mob*, time_t);
void combatStep(Mob*, time_t);
@ -96,6 +97,7 @@ namespace MobManager {
void combatBegin(CNSocket *sock, CNPacketData *data);
void combatEnd(CNSocket *sock, CNPacketData *data);
void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
void dealGooDamage(CNSocket *sock, int amount);
void npcAttackPc(Mob *mob);
int hitMob(CNSocket *sock, Mob *mob, int damage);

View File

@ -517,11 +517,13 @@ void activePower(CNSocket *sock, CNPacketData *data,
sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
Player *plr = PlayerManager::getPlayer(sock);
plr->Nanos[plr->activeNano].iStamina -= 40;
resp->iPC_ID = plr->iID;
resp->iSkillID = skillId;
resp->iNanoID = nanoId;
resp->iNanoStamina = 150;
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
resp->eST = eSkillType;
resp->iTargetCnt = pkt->iTargetCnt;
@ -570,7 +572,7 @@ void NanoManager::nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t
resp->iPC_ID = plr->iID;
resp->iSkillID = skillId;
resp->iNanoID = nanoId;
resp->iNanoStamina = 150;
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
resp->eST = eSkillType;
resp->iTargetCnt = 1;

View File

@ -44,6 +44,9 @@ struct Player {
bool isTrading;
bool isTradeConfirm;
bool inCombat;
bool dotDamage;
int64_t aQuestFlag[16];
int tasks[ACTIVE_MISSION_COUNT];
int RemainingNPCCount[ACTIVE_MISSION_COUNT][3];