mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 13:30:06 +00:00
d32827b692
Was getting frustrated by the inconsistency in our include statements, which were causing me problems. As a result, I went through and manually re-organized every include statement in non-core files. I'm just gonna copy my rant from Discord: FOR HEADER FILES (.hpp): - everything you use IN THE HEADER must be EXPLICITLY INCLUDED with the exception of things that fall under Core.hpp - you may NOT include ANYTHING ELSE FOR SOURCE FILES (.cpp): - you can #include whatever you want as long as the partner header is included first - anything that gets included by another include is fair game - redundant includes are ok because they'll be harmless AS LONG AS our header files stay lean. the point of this is NOT to optimize the number of includes used all around or make things more efficient necessarily. it's to improve readability & coherence and make it easier to avoid cyclical issues
429 lines
16 KiB
C++
429 lines
16 KiB
C++
#include "Transport.hpp"
|
|
|
|
#include "servers/CNShardServer.hpp"
|
|
|
|
#include "PlayerManager.hpp"
|
|
#include "Nanos.hpp"
|
|
#include "TableData.hpp"
|
|
#include "Entities.hpp"
|
|
#include "NPCManager.hpp"
|
|
#include "MobAI.hpp"
|
|
|
|
#include <unordered_map>
|
|
#include <cmath>
|
|
|
|
using namespace Transport;
|
|
|
|
std::map<int32_t, TransportRoute> Transport::Routes;
|
|
std::map<int32_t, TransportLocation> Transport::Locations;
|
|
std::vector<NPCPath> Transport::NPCPaths;
|
|
std::map<int32_t, std::queue<Vec3>> Transport::SkywayPaths;
|
|
std::unordered_map<CNSocket*, std::queue<Vec3>> Transport::SkywayQueues;
|
|
std::unordered_map<int32_t, std::queue<Vec3>> Transport::NPCQueues;
|
|
|
|
static void transportRegisterLocationHandler(CNSocket* sock, CNPacketData* data) {
|
|
auto transport = (sP_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION*)data->buf;
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
|
|
|
bool newReg = false; // this is a new registration
|
|
//std::cout << "request to register transport, eTT " << transport->eTT << ", locID " << transport->iLocationID << ", npc " << transport->iNPC_ID << std::endl;
|
|
if (transport->eTT == 1) { // S.C.A.M.P.E.R.
|
|
if (transport->iLocationID < 1 || transport->iLocationID > 31) { // sanity check
|
|
std::cout << "[WARN] S.C.A.M.P.E.R. location ID " << transport->iLocationID << " is out of bounds" << std::endl;
|
|
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
|
|
|
|
failResp.eTT = transport->eTT;
|
|
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
|
failResp.iLocationID = transport->iLocationID;
|
|
|
|
sock->sendPacket(failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL);
|
|
return;
|
|
}
|
|
|
|
// update registration bitfield using bitmask
|
|
uint32_t newScamperFlag = plr->iWarpLocationFlag | (1UL << (transport->iLocationID - 1));
|
|
if (newScamperFlag != plr->iWarpLocationFlag) {
|
|
plr->iWarpLocationFlag = newScamperFlag;
|
|
newReg = true;
|
|
}
|
|
} else if (transport->eTT == 2) { // Monkey Skyway System
|
|
if (transport->iLocationID < 1 || transport->iLocationID > 127) { // sanity check
|
|
std::cout << "[WARN] Skyway location ID " << transport->iLocationID << " is out of bounds" << std::endl;
|
|
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
|
|
|
|
failResp.eTT = transport->eTT;
|
|
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
|
failResp.iLocationID = transport->iLocationID;
|
|
|
|
sock->sendPacket(failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* assuming the two bitfields are just stuck together to make a longer one, do a similar operation
|
|
*/
|
|
int index = transport->iLocationID > 64 ? 1 : 0;
|
|
uint64_t newMonkeyFlag = plr->aSkywayLocationFlag[index] | (1ULL << (index ? transport->iLocationID - 65 : transport->iLocationID - 1));
|
|
if (newMonkeyFlag != plr->aSkywayLocationFlag[index]) {
|
|
plr->aSkywayLocationFlag[index] = newMonkeyFlag;
|
|
newReg = true;
|
|
}
|
|
} else {
|
|
std::cout << "[WARN] Unknown mode of transport; eTT = " << transport->eTT << std::endl;
|
|
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL, failResp);
|
|
|
|
failResp.eTT = transport->eTT;
|
|
failResp.iErrorCode = 0; // TODO: review what error code to use here
|
|
failResp.iLocationID = transport->iLocationID;
|
|
|
|
sock->sendPacket(failResp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_FAIL);
|
|
return;
|
|
}
|
|
|
|
if (!newReg)
|
|
return; // don't send new registration message
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC, resp);
|
|
// response parameters
|
|
resp.eTT = transport->eTT;
|
|
resp.iLocationID = transport->iLocationID;
|
|
resp.iWarpLocationFlag = plr->iWarpLocationFlag;
|
|
resp.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
|
|
resp.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
|
|
|
|
sock->sendPacket(resp, P_FE2CL_REP_PC_REGIST_TRANSPORTATION_LOCATION_SUCC);
|
|
}
|
|
|
|
static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
|
auto req = (sP_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION*)data->buf;
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
|
|
|
/*
|
|
* req:
|
|
* eIL -- inventory type
|
|
* iNPC_ID -- the ID of the NPC who is warping you
|
|
* iTransporationID -- iVehicleID
|
|
* iSlotNum -- inventory slot number
|
|
*/
|
|
|
|
if (Routes.find(req->iTransporationID) == Routes.end() || Routes[req->iTransporationID].cost > plr->money) { // sanity check
|
|
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL, failResp);
|
|
|
|
failResp.iErrorCode = 0; // TODO: error code
|
|
failResp.iTransportationID = req->iTransporationID;
|
|
|
|
sock->sendPacket(failResp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_FAIL);
|
|
return;
|
|
}
|
|
|
|
TransportRoute route = Routes[req->iTransporationID];
|
|
plr->money -= route.cost;
|
|
|
|
TransportLocation* target = nullptr;
|
|
switch (route.type) {
|
|
case 1: // S.C.A.M.P.E.R.
|
|
target = &Locations[route.end];
|
|
break;
|
|
case 2: // Monkey Skyway
|
|
// set last safe coords
|
|
plr->lastX = plr->x;
|
|
plr->lastY = plr->y;
|
|
plr->lastZ = plr->z;
|
|
if (SkywayPaths.find(route.mssRouteNum) != SkywayPaths.end()) { // check if route exists
|
|
Nanos::summonNano(sock, -1); // make sure that no nano is active during the ride
|
|
SkywayQueues[sock] = SkywayPaths[route.mssRouteNum]; // set socket point queue to route
|
|
plr->onMonkey = true;
|
|
break;
|
|
} else if (TableData::RunningSkywayRoutes.find(route.mssRouteNum) != TableData::RunningSkywayRoutes.end()) {
|
|
std::vector<Vec3>* _route = &TableData::RunningSkywayRoutes[route.mssRouteNum];
|
|
Nanos::summonNano(sock, -1);
|
|
testMssRoute(sock, _route);
|
|
plr->onMonkey = true;
|
|
break;
|
|
}
|
|
|
|
// refund and send alert packet
|
|
plr->money += route.cost;
|
|
INITSTRUCT(sP_FE2CL_ANNOUNCE_MSG, alert);
|
|
alert.iAnnounceType = 0; // don't think this lets us make a confirm dialog
|
|
alert.iDuringTime = 3;
|
|
U8toU16("Skyway route " + std::to_string(route.mssRouteNum) + " isn't pathed yet. You will not be charged any taros.", (char16_t*)alert.szAnnounceMsg, sizeof(alert.szAnnounceMsg));
|
|
sock->sendPacket(alert, P_FE2CL_ANNOUNCE_MSG);
|
|
|
|
std::cout << "[WARN] MSS route " << route.mssRouteNum << " not pathed" << std::endl;
|
|
break;
|
|
default:
|
|
std::cout << "[WARN] Unknown tranportation type " << route.type << std::endl;
|
|
break;
|
|
}
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC, resp);
|
|
// response parameters
|
|
resp.eTT = route.type;
|
|
resp.iCandy = plr->money;
|
|
resp.iX = (target == nullptr) ? plr->x : target->x;
|
|
resp.iY = (target == nullptr) ? plr->y : target->y;
|
|
resp.iZ = (target == nullptr) ? plr->z : target->z;
|
|
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_TRANSPORTATION_SUCC);
|
|
|
|
if (target == nullptr)
|
|
return;
|
|
|
|
// we warped; update position and chunks
|
|
PlayerManager::updatePlayerPositionForWarp(sock, target->x, target->y, target->z, INSTANCE_OVERWORLD);
|
|
}
|
|
|
|
void Transport::testMssRoute(CNSocket *sock, std::vector<Vec3>* route) {
|
|
int speed = 1500; // TODO: make this adjustable
|
|
std::queue<Vec3> path;
|
|
Vec3 last = route->front(); // start pos
|
|
|
|
for (int i = 1; i < route->size(); i++) {
|
|
Vec3 coords = route->at(i);
|
|
Transport::lerp(&path, last, coords, speed);
|
|
path.push(coords); // add keyframe to the queue
|
|
last = coords; // update start pos
|
|
}
|
|
|
|
SkywayQueues[sock] = path;
|
|
}
|
|
|
|
/*
|
|
* Go through every socket that has broomstick points queued up, and advance to the next point.
|
|
* If the player has disconnected or finished the route, clean up and remove them from the queue.
|
|
*/
|
|
static void stepSkywaySystem() {
|
|
|
|
// using an unordered map so we can remove finished players in one iteration
|
|
std::unordered_map<CNSocket*, std::queue<Vec3>>::iterator it = SkywayQueues.begin();
|
|
while (it != SkywayQueues.end()) {
|
|
|
|
std::queue<Vec3>* queue = &it->second;
|
|
|
|
if (PlayerManager::players.find(it->first) == PlayerManager::players.end()) {
|
|
// pluck out dead socket + update iterator
|
|
it = SkywayQueues.erase(it);
|
|
continue;
|
|
}
|
|
|
|
Player* plr = PlayerManager::getPlayer(it->first);
|
|
|
|
if (queue->empty()) {
|
|
// send dismount packet
|
|
INITSTRUCT(sP_FE2CL_REP_PC_RIDING_SUCC, rideSucc);
|
|
INITSTRUCT(sP_FE2CL_PC_RIDING, rideBroadcast);
|
|
rideSucc.iPC_ID = plr->iID;
|
|
rideSucc.eRT = 0;
|
|
rideBroadcast.iPC_ID = plr->iID;
|
|
rideBroadcast.eRT = 0;
|
|
it->first->sendPacket(rideSucc, P_FE2CL_REP_PC_RIDING_SUCC);
|
|
// send packet to players in view
|
|
PlayerManager::sendToViewable(it->first, rideBroadcast, P_FE2CL_PC_RIDING);
|
|
it = SkywayQueues.erase(it); // remove player from tracking map + update iterator
|
|
plr->onMonkey = false;
|
|
} else {
|
|
Vec3 point = queue->front(); // get point
|
|
queue->pop(); // remove point from front of queue
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_BROOMSTICK_MOVE, bmstk);
|
|
bmstk.iPC_ID = plr->iID;
|
|
bmstk.iToX = point.x;
|
|
bmstk.iToY = point.y;
|
|
bmstk.iToZ = point.z;
|
|
it->first->sendPacket(bmstk, P_FE2CL_PC_BROOMSTICK_MOVE);
|
|
// set player location to point to update viewables
|
|
PlayerManager::updatePlayerPosition(it->first, point.x, point.y, point.z, plr->instanceID, plr->angle);
|
|
// send packet to players in view
|
|
PlayerManager::sendToViewable(it->first, bmstk, P_FE2CL_PC_BROOMSTICK_MOVE);
|
|
|
|
it++; // go to next entry in map
|
|
}
|
|
}
|
|
}
|
|
|
|
static void stepNPCPathing() {
|
|
|
|
// all NPC pathing queues
|
|
std::unordered_map<int32_t, std::queue<Vec3>>::iterator it = NPCQueues.begin();
|
|
while (it != NPCQueues.end()) {
|
|
|
|
std::queue<Vec3>* queue = &it->second;
|
|
|
|
BaseNPC* npc = nullptr;
|
|
if (NPCManager::NPCs.find(it->first) != NPCManager::NPCs.end())
|
|
npc = NPCManager::NPCs[it->first];
|
|
|
|
if (npc == nullptr || queue->empty()) {
|
|
// pluck out dead path + update iterator
|
|
it = NPCQueues.erase(it);
|
|
continue;
|
|
}
|
|
|
|
// skip if not simulating mobs
|
|
if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) {
|
|
it++;
|
|
continue;
|
|
}
|
|
|
|
// do not roam if not roaming
|
|
if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
|
|
it++;
|
|
continue;
|
|
}
|
|
|
|
Vec3 point = queue->front(); // get point
|
|
queue->pop(); // remove point from front of queue
|
|
|
|
// calculate displacement
|
|
int dXY = hypot(point.x - npc->x, point.y - npc->y); // XY plane distance
|
|
int distanceBetween = hypot(dXY, point.z - npc->z); // total distance
|
|
|
|
// update NPC location to update viewables
|
|
NPCManager::updateNPCPosition(npc->id, point.x, point.y, point.z, npc->instanceID, npc->angle);
|
|
|
|
// TODO: move walking logic into Entity stack
|
|
switch (npc->kind) {
|
|
case EntityKind::BUS:
|
|
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
|
|
|
busMove.eTT = 3;
|
|
busMove.iT_ID = npc->id;
|
|
busMove.iMoveStyle = 0; // ???
|
|
busMove.iToX = point.x;
|
|
busMove.iToY = point.y;
|
|
busMove.iToZ = point.z;
|
|
busMove.iSpeed = distanceBetween; // set to distance to match how monkeys work
|
|
|
|
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
|
break;
|
|
case EntityKind::MOB:
|
|
MobAI::incNextMovement((Mob*)npc);
|
|
/* fallthrough */
|
|
default:
|
|
INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
|
|
move.iNPC_ID = npc->id;
|
|
move.iMoveStyle = 0; // ???
|
|
move.iToX = point.x;
|
|
move.iToY = point.y;
|
|
move.iToZ = point.z;
|
|
move.iSpeed = distanceBetween;
|
|
|
|
NPCManager::sendToViewable(npc, &move, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If this path should be repeated, move processed point to the back to maintain cycle.
|
|
*/
|
|
if (npc->loopingPath)
|
|
queue->push(point);
|
|
|
|
it++; // go to next entry in map
|
|
}
|
|
}
|
|
|
|
static void tickTransportationSystem(CNServer* serv, time_t currTime) {
|
|
stepNPCPathing();
|
|
stepSkywaySystem();
|
|
}
|
|
|
|
/*
|
|
* Linearly interpolate between two points and insert the results into a queue.
|
|
*/
|
|
void Transport::lerp(std::queue<Vec3>* queue, Vec3 start, Vec3 end, int gapSize, float curve) {
|
|
int dXY = hypot(end.x - start.x, end.y - start.y); // XY plane distance
|
|
int distanceBetween = hypot(dXY, end.z - start.z); // total distance
|
|
int lerps = distanceBetween / gapSize; // number of intermediate points to add
|
|
for (int i = 1; i <= lerps; i++) {
|
|
Vec3 lerp;
|
|
// lerp math
|
|
//float frac = i / (lerps + 1);
|
|
float frac = powf(i, curve) / powf(lerps + 1, curve);
|
|
lerp.x = (start.x * (1.0f - frac)) + (end.x * frac);
|
|
lerp.y = (start.y * (1.0f - frac)) + (end.y * frac);
|
|
lerp.z = (start.z * (1.0f - frac)) + (end.z * frac);
|
|
queue->push(lerp); // add lerp'd point
|
|
}
|
|
}
|
|
void Transport::lerp(std::queue<Vec3>* queue, Vec3 start, Vec3 end, int gapSize) {
|
|
lerp(queue, start, end, gapSize, 1);
|
|
}
|
|
|
|
/*
|
|
* Find and return the first path that targets either the type or the ID.
|
|
* If no matches are found, return nullptr
|
|
*/
|
|
NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) {
|
|
NPCPath* match = nullptr;
|
|
for (auto _path = Transport::NPCPaths.begin(); _path != Transport::NPCPaths.end(); _path++) {
|
|
|
|
// task ID for the path must match so escorts don't start early
|
|
if (_path->escortTaskID != taskID)
|
|
continue;
|
|
|
|
// search target IDs
|
|
for (int32_t pID : _path->targetIDs) {
|
|
if (id == pID) {
|
|
match = &(*_path);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match != nullptr)
|
|
break; // early break for ID matches, since ID has higher priority than type
|
|
|
|
// search target types
|
|
for (int32_t pType : _path->targetTypes) {
|
|
if (type == pType) {
|
|
match = &(*_path);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match != nullptr)
|
|
break;
|
|
}
|
|
return match;
|
|
}
|
|
|
|
void Transport::constructPathNPC(int32_t id, NPCPath* path) {
|
|
BaseNPC* npc = NPCManager::NPCs[id];
|
|
if (npc->kind == EntityKind::MOB)
|
|
((Mob*)(npc))->staticPath = true;
|
|
npc->loopingPath = path->isLoop;
|
|
|
|
// Interpolate
|
|
std::vector<Vec3> pathPoints = path->points;
|
|
std::queue<Vec3> points;
|
|
|
|
auto _point = pathPoints.begin();
|
|
Vec3 from = *_point; // point A coords
|
|
for (_point++; _point != pathPoints.end(); _point++) { // loop through all point Bs
|
|
Vec3 to = *_point; // point B coords
|
|
// add point A to the queue
|
|
if (path->isRelative) {
|
|
// relative; the NPCs current position is assumed to be its spawn point
|
|
Vec3 fromReal = { from.x + npc->x, from.y + npc->y, from.z + npc->z };
|
|
Vec3 toReal = { to.x + npc->x, to.y + npc->y, to.z + npc->z };
|
|
points.push(fromReal);
|
|
Transport::lerp(&points, fromReal, toReal, path->speed); // lerp from A to B
|
|
}
|
|
else {
|
|
// absolute
|
|
points.push(from);
|
|
Transport::lerp(&points, from, to, path->speed); // lerp from A to B
|
|
}
|
|
|
|
from = to; // update point A
|
|
}
|
|
|
|
Transport::NPCQueues[id] = points;
|
|
}
|
|
|
|
void Transport::init() {
|
|
REGISTER_SHARD_TIMER(tickTransportationSystem, 1000);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_REGIST_TRANSPORTATION_LOCATION, transportRegisterLocationHandler);
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_USE_TRANSPORTATION, transportWarpHandler);
|
|
}
|