Compare commits

..

56 Commits

Author SHA1 Message Date
gsemaj
bbaaa53df2 [WIP] Active power handling
TODO:
- recall (self and group) is broken
- revive (only group) is broken
- damage + debuff is unimplemented
2022-11-27 17:39:08 -05:00
gsemaj
273b73379e Sync with master 2022-07-30 22:50:03 -07:00
gsemaj
1709d2b0ef Add overload to remove specific class of buff
I initially added this because, despite the higher tickrate for
composite condition calculations thanks to the last commit, there is
still a slight status icon delay when rapidly switching nanos. I
attempted to use this to make that problem go away and for whatever
reason it wasn't effective, but I figure it would be useful to have
anyway so I'm keeping it.
2022-07-30 22:35:20 -07:00
gsemaj
457c320d48 Move some stuff from playerTick to player combat step 2022-07-30 22:35:19 -07:00
gsemaj
29024e9351 Refactor group handling 2022-07-30 22:35:19 -07:00
gsemaj
0994aaff71 Port egg buffs over to new system 2022-07-30 22:35:19 -07:00
gsemaj
cb94018e46 More skill handlers
Note: need to revisit these when active powers are implemented to make
sure they are correct. DamageNDebuff isn't even implemented yet.
2022-07-30 22:35:18 -07:00
gsemaj
e0a0cc186e Passive nano powers 2022-07-30 22:35:18 -07:00
gsemaj
d4253c3b49 YET ANOTHER ITERATION of the new ability system
I am very tired
2022-07-30 22:35:17 -07:00
gsemaj
5b58055924 Passive nano powers pt 1 2022-07-30 22:35:17 -07:00
gsemaj
7a7cdb1330 Passive nano powers boilerplate 2022-07-30 22:35:16 -07:00
gsemaj
0e054e21b6 Fix timed out buffs not calling onExpire 2022-07-30 22:35:16 -07:00
gsemaj
ad9bf2a9e3 Buff framework tweaks + polish 2022-07-30 22:35:16 -07:00
gsemaj
a38bf0e7be Rework buff callbacks
The first implementation was way too complicated and prone to bugs.
This is much more simple flexible; first off, std::function is now used
instead of a raw function pointer, so lambdas and binds are fair game
which is great for scripting. Second, callbacks for all stacks are
executed. It is up to the callback target to ensure correct behavior.
2022-07-30 22:35:15 -07:00
gsemaj
3d22103113 oops 2022-07-30 22:35:15 -07:00
gsemaj
761581afd7 CRLF purge in Buffs.cpp 2022-07-30 22:35:15 -07:00
gsemaj
daec7a33b7 egg prep 2022-07-30 22:35:14 -07:00
gsemaj
462ced6685 Move Buff implementation to Buffs.cpp 2022-07-30 22:35:14 -07:00
gsemaj
cc190efc63 New buff framework (player implementation)
Get rid of `iConditionBitFlag` in favor of a system of individual buff
objects that get composited to a bitflag on-the-fly.
Buff objects can have callbacks for application, expiration, and tick,
making them pretty flexible. Scripting languages can eventually use
these for custom behavior, too.

TODO:
- Get rid of bitflag in BaseNPC
- Apply buffs from passive nano powers
- Apply buffs from active nano powers
- Move eggs to new system
- ???
2022-07-30 22:35:14 -07:00
gsemaj
df1b4ff160 The great re-#include
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
2022-07-30 22:35:13 -07:00
gsemaj
9f7c8a18df Get rid of player fire rate suspicion
This was super primitive & jank, and caused false positives.
Will replace with a polished system later on.
2022-07-30 22:32:25 -07:00
gsemaj
ea61ed1aaa Handle case where cmake is invoked outside root 2022-07-30 22:32:25 -07:00
gsemaj
9982d9bdda Start moving passive power processing to playerTick 2022-07-30 22:32:25 -07:00
gsemaj
398e9fddf8 Groundwork for new buff system 2022-07-30 22:32:24 -07:00
gsemaj
fd664c4f19 Active power classification 2022-07-30 22:32:24 -07:00
gsemaj
feaaf4cab9 some struct reorg 2022-07-30 22:32:24 -07:00
gsemaj
d3e6bae5d9 Replace group filter operator with function 2022-07-30 22:32:24 -07:00
gsemaj
0f042c4233 Ignore .bak files
for my local backups lol
2022-07-30 22:32:24 -07:00
gsemaj
3dc7124f58 Refactor player groups
Group structures are used now. Adds more checks in some places but simplifies things overall.
We can expand this system to entities as well now pretty trivially.
2022-07-30 22:32:23 -07:00
gsemaj
71e78afa0b (WIP) EXPERIMENTAL GROUP CHANGES 2022-07-30 22:32:23 -07:00
gsemaj
0a2b3fbdad (WIP) TODO ABILITIES 2022-07-30 22:31:18 -07:00
gsemaj
911dbaab83 Move mob aggro logic into takeDamage override
God that feels good
2022-07-30 22:31:18 -07:00
gsemaj
7183180be5 (WIP) Move away from rigid states/transitions to allow custom behavior 2022-07-30 22:31:18 -07:00
gsemaj
3f1f78942e EntityType -> EntityKind 2022-07-30 22:31:18 -07:00
gsemaj
a9dea36882 (WIP) onRoamStart hook implementation 2022-07-30 22:31:17 -07:00
gsemaj
de23779209 (WIP) Remove BaseNPC::barkerType to save space 2022-07-30 22:31:17 -07:00
gsemaj
748a82e223 ope 2022-07-30 22:31:17 -07:00
gsemaj
231e88fd55 (WIP) onCombatStart hook implementation 2022-07-30 22:31:17 -07:00
gsemaj
abfb562489 (WIP) onDeath hook implementation 2022-07-30 22:31:17 -07:00
gsemaj
589c5a8732 (WIP) Add src param to transition + certain hooks
Should all hooks have src? I think not
2022-07-30 22:31:17 -07:00
gsemaj
0b8b92b7f6 (WIP) Transitions + hook definitions + onRetreat hook implementation 2022-07-30 22:31:17 -07:00
gsemaj
f0cf6326e5 (WIP) Point 2: Generalization 2022-07-30 22:31:16 -07:00
gsemaj
82bc94c01c (WIP) Point 1: step functions 2022-07-30 22:29:14 -07:00
gsemaj
5d9dcb8609 (WIP) Start implementing ICombatant
Start by replacing `hitMob` with `takeDamage` interface function.
Simplify `pcAttackChars` a little by utilizing the new interface, then add more interface functions as needed.

A lot of the combat logic is tied to the `Mob` class. Need to start moving stuff over to CombatNPC.
2022-07-30 22:29:13 -07:00
gsemaj
166e148878 (WIP) Move ICombatant functions around a bit 2022-07-30 22:29:12 -07:00
gsemaj
14b02ec5d2 (WIP) Initial ICombatant draft 2022-07-30 22:29:12 -07:00
d50c312227 [refactor] Get rid of NPC.hpp
This file was already obsoleted at the start of the refactor, but seems
to have escaped notice until now.
2022-07-30 22:29:10 -07:00
18ed96493f [refactor] Replace a few uses of magic numbers with enums 2022-07-30 22:29:10 -07:00
71a7d3d164 [refactor] Cosmetic cleanup in Fuse fight functions 2022-07-30 22:29:08 -07:00
520efd6dd5 [refactor] Remove redundant coord args from most entity constructors
Mobs and CombatNPCs still need theirs in order to properly set their
roaming and spawn coords. Assignment of the latter has been moved to the
CombatNPC constructor, where it should have already been.
2022-07-30 22:29:08 -07:00
gsemaj
0a8ef2ebbd [WIP] Stub power handler 2022-07-30 22:29:06 -07:00
gsemaj
3f4aca8a5d [WIP] Use EntityRef instead of CNSocket in ability handler 2022-07-30 22:29:06 -07:00
gsemaj
b0de75d80e [WIP] Replace appearance data with individual fields
Storing certain things in appearance data and others in their own fields
was gross. Now everything is stored on the same level and functions have
been added to generate appearance data when it's needed by the client.
2022-07-30 22:29:05 -07:00
gsemaj
8f88edaad1 [WIP] Rename Entity.type -> Entity.kind 2022-07-30 22:29:04 -07:00
gsemaj
6896d35427 [WIP] Fix Nanos -> Abilities namespace calls 2022-07-30 22:29:01 -07:00
gsemaj
d74a9747d9 [WIP] Initial merge of ability namespaces & features 2022-07-30 22:29:00 -07:00
34 changed files with 185 additions and 485 deletions

26
.vscode/launch.json vendored
View File

@ -1,26 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug (Linux)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/fusion",
"cwd": "${workspaceFolder}"
},
{
"name": "Debug (Windows)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/Debug/winfusion.exe",
"cwd": "${workspaceFolder}"
},
{
"name": "Release (Windows)",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/Release/winfusion.exe",
"cwd": "${workspaceFolder}"
}
]
}

View File

@ -45,7 +45,7 @@ add_executable(openfusion ${SOURCES})
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME}) set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
# find sqlite3 and use it # find sqlite3 and use it
find_package(SQLite3 REQUIRED) find_package(sqlite3 REQUIRED)
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS}) target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES}) target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
@ -57,5 +57,5 @@ set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_S
# It's not something you should do, but it's there if you need it... # It's not something you should do, but it's there if you need it...
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles") if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
target_link_libraries(openfusion PRIVATE pthread) target_link_libraries(openfusion pthread)
endif() endif()

View File

@ -1,21 +0,0 @@
FROM debian:latest
WORKDIR /usr/src/app
RUN apt-get -y update && apt-get install -y \
git \
clang \
make \
libsqlite3-dev
COPY . ./
RUN make -j8
# tabledata should be copied from the host;
# clone it there before building the container
#RUN git submodule update --init --recursive
CMD ["./bin/fusion"]
LABEL Name=openfusion Version=0.0.1

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2023 OpenFusion Contributors Copyright (c) 2020-2022 OpenFusion Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -25,8 +25,6 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen. 4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4. 5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4.
Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux).
### Hosting a server ### Hosting a server
1. Grab `OpenFusionServer-1.4-original.zip` or `OpenFusionServer-1.4-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.4). 1. Grab `OpenFusionServer-1.4-original.zip` or `OpenFusionServer-1.4-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.4).
@ -37,7 +35,7 @@ Instructions for getting the client to run on Linux through Wine can be found [h
3. Lastly Game Version - select `beta-20100104` if you downloaded the original zip, or `beta-20111013` if you downloaded the academy zip. 3. Lastly Game Version - select `beta-20100104` if you downloaded the original zip, or `beta-20111013` if you downloaded the academy zip.
5. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section. 5. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
If you want to run the latest development builds of the server, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/) If you want, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/)
For a more detailed overview of the game's architecture and how to configure it, read the following sections. For a more detailed overview of the game's architecture and how to configure it, read the following sections.
@ -83,7 +81,7 @@ This just works if you're all under the same LAN, but if you want to play over t
## Compiling ## Compiling
OpenFusion has one external dependency: SQLite. The oldest compatible version is `3.33.0`. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg). OpenFusion has one external dependency: SQLite. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg).
You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file. You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file.

View File

@ -1,12 +0,0 @@
version: '3.4'
services:
openfusion:
image: openfusion
build:
context: .
dockerfile: ./Dockerfile
ports:
- "23000:23000"
- "23001:23001"
- "8003:8003"

View File

@ -270,30 +270,10 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen); NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
} }
static std::vector<ICombatant*> entityRefsToCombatants(std::vector<EntityRef> refs) { std::vector<ICombatant*> Abilities::matchTargets(SkillData* skill, int count, int32_t *ids) {
std::vector<ICombatant*> combatants;
for(EntityRef ref : refs) {
if(ref.kind == EntityKind::PLAYER)
combatants.push_back(dynamic_cast<ICombatant*>(PlayerManager::getPlayer(ref.sock)));
else if(ref.kind == EntityKind::COMBAT_NPC || ref.kind == EntityKind::MOB)
combatants.push_back(dynamic_cast<ICombatant*>(ref.getEntity()));
}
return combatants;
}
std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* skill, int count, int32_t *ids) {
if(skill->targetType == SkillTargetType::GROUP) {
// group
if(count != 1 || ids[0] != src->getID()) {
std::cout << "[WARN] skill: bad group targeting (id " << ids[0] << ")\n";
return {};
}
return entityRefsToCombatants(src->getGroupMembers());
}
// individuals
std::vector<ICombatant*> targets; std::vector<ICombatant*> targets;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int32_t id = ids[i]; int32_t id = ids[i];
if (skill->targetType == SkillTargetType::MOBS) { if (skill->targetType == SkillTargetType::MOBS) {
@ -306,8 +286,8 @@ std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* ski
} }
} }
std::cout << "[WARN] skill: invalid mob target (id " << id << ")\n"; std::cout << "[WARN] skill: invalid mob target (id " << id << ")\n";
} else if(skill->targetType == SkillTargetType::PLAYERS) { } else if(skill->targetType == SkillTargetType::SELF || skill->targetType == SkillTargetType::GROUP) {
// player // players (?)
Player* plr = PlayerManager::getPlayerFromID(id); Player* plr = PlayerManager::getPlayerFromID(id);
if (plr != nullptr) { if (plr != nullptr) {
targets.push_back(dynamic_cast<ICombatant*>(plr)); targets.push_back(dynamic_cast<ICombatant*>(plr));

View File

@ -21,7 +21,7 @@ enum class SkillEffectTarget {
enum class SkillTargetType { enum class SkillTargetType {
MOBS = 1, MOBS = 1,
PLAYERS = 2, SELF = 2,
GROUP = 3 GROUP = 3
}; };
@ -63,6 +63,6 @@ namespace Abilities {
void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>); void useNanoSkill(CNSocket*, SkillData*, sNano&, std::vector<ICombatant*>);
void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>); void useNPCSkill(EntityRef, int skillID, std::vector<ICombatant*>);
std::vector<ICombatant*> matchTargets(ICombatant*, SkillData*, int, int32_t*); std::vector<ICombatant*> matchTargets(SkillData*, int, int32_t*);
int getCSTBFromST(int eSkillType); int getCSTBFromST(int eSkillType);
} }

View File

@ -304,40 +304,11 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
return ret; return ret;
} }
static bool checkRapidFire(CNSocket *sock, int targetCount) {
Player *plr = PlayerManager::getPlayer(sock);
time_t currTime = getTime();
if (currTime - plr->lastShot < plr->fireRate * 80)
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // lose suspicion for delayed firing
plr->lastShot = currTime;
// 3+ targets should never be possible
if (targetCount > 3)
plr->suspicionRating += 10001;
// kill the socket when the player is too suspicious
if (plr->suspicionRating > 10000) {
sock->kill();
CNShardServer::_killConnection(sock);
return true;
}
return false;
}
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) { static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf; auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
auto targets = (int32_t*)data->trailers; auto targets = (int32_t*)data->trailers;
// kick the player if firing too rapidly
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt))
return;
/* /*
* IMPORTANT: This validates memory safety in addition to preventing * IMPORTANT: This validates memory safety in addition to preventing
* ordinary cheating. If the client sends a very large number of trailing * ordinary cheating. If the client sends a very large number of trailing

View File

@ -29,7 +29,7 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
SkillData* skill = &Abilities::SkillTable[skillId]; SkillData* skill = &Abilities::SkillTable[skillId];
if(skill->drainType == SkillDrainType::PASSIVE) { if(skill->drainType == SkillDrainType::PASSIVE) {
// apply buff // apply buff
if(skill->targetType != SkillTargetType::PLAYERS) { if(skill->targetType != SkillTargetType::SELF) {
std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl; std::cout << "[WARN] weird skill type for egg " << eggId << " with skill " << skillId << ", should be " << (int)skill->targetType << std::endl;
} }

View File

@ -93,7 +93,7 @@ static void emailReceiveItemSingle(CNSocket* sock, CNPacketData* data) {
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf; auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4) if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iSlotNum < 1 || pkt->iSlotNum > 4)
return; // sanity check return; // sanity check
// get email item from db and delete it // get email item from db and delete it
@ -252,26 +252,11 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
if (attachment.ItemInven.iID == 0) if (attachment.ItemInven.iID == 0)
continue; continue;
sItemBase* item = &pkt->aItem[i].ItemInven;
sItemBase* real = &plr->Inven[attachment.iSlotNum];
resp.aItem[i] = attachment; resp.aItem[i] = attachment;
attachments.push_back(attachment.ItemInven); attachments.push_back(attachment.ItemInven);
attSlots.push_back(attachment.iSlotNum); attSlots.push_back(attachment.iSlotNum);
if (real->iOpt <= item->iOpt) // delete item (if they attached the whole stack) // delete item
*real = { 0, 0, 0, 0 }; plr->Inven[attachment.iSlotNum] = { 0, 0, 0, 0 };
else // otherwise, decrement the item
real->iOpt -= item->iOpt;
// HACK: update the slot
INITSTRUCT(sP_FE2CL_PC_ITEM_MOVE_SUCC, itemResp);
itemResp.iFromSlotNum = attachment.iSlotNum;
itemResp.iToSlotNum = attachment.iSlotNum;
itemResp.FromSlotItem = *real;
itemResp.ToSlotItem = *real;
itemResp.eFrom = (int32_t)Items::SlotType::INVENTORY;
itemResp.eTo = (int32_t)Items::SlotType::INVENTORY;
sock->sendPacket(itemResp, P_FE2CL_PC_ITEM_MOVE_SUCC);
} }
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
@ -291,7 +276,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
0 // DeleteTime (unimplemented) 0 // DeleteTime (unimplemented)
}; };
if (!Database::sendEmail(&email, attachments, plr)) { if (!Database::sendEmail(&email, attachments)) {
plr->money += cost; // give money back plr->money += cost; // give money back
// give items back // give items back
while (!attachments.empty()) { while (!attachments.empty()) {

View File

@ -73,6 +73,16 @@ std::vector<ICombatant*> Nanos::applyNanoBuff(SkillData* skill, Player* plr) {
assert(skill->drainType == SkillDrainType::PASSIVE); assert(skill->drainType == SkillDrainType::PASSIVE);
EntityRef self = PlayerManager::getSockFromID(plr->iID); EntityRef self = PlayerManager::getSockFromID(plr->iID);
std::vector<ICombatant*> affected;
std::vector<EntityRef> targets;
if (skill->targetType == SkillTargetType::GROUP) {
targets = plr->getGroupMembers(); // group
}
else if(skill->targetType == SkillTargetType::SELF) {
targets.push_back(self); // self
} else {
std::cout << "[WARN] Passive skill with type " << skill->skillType << " has target type MOB" << std::endl;
}
int timeBuffId = Abilities::getCSTBFromST(skill->skillType); int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
int boost = Nanos::getNanoBoost(plr) ? 3 : 0; int boost = Nanos::getNanoBoost(plr) ? 3 : 0;
@ -85,22 +95,21 @@ std::vector<ICombatant*> Nanos::applyNanoBuff(SkillData* skill, Player* plr) {
BuffClass::NONE, // overwritten per target BuffClass::NONE, // overwritten per target
}; };
// for passive skills, using just the player as a target is fine for (EntityRef target : targets) {
// this is because the group skill type will ignore the count, Entity* entity = target.getEntity();
// and the other option is single-target if (entity->kind != PLAYER && entity->kind != COMBAT_NPC && entity->kind != MOB)
std::vector<ICombatant*> targets = Abilities::matchTargets(dynamic_cast<ICombatant*>(plr), skill, 1, &plr->iID); continue; // not a combatant
std::vector<ICombatant*> affected;
for (ICombatant* target : targets) {
passiveBuff.buffStackClass = target == plr ? BuffClass::NANO : BuffClass::GROUP_NANO; passiveBuff.buffStackClass = target == self ? BuffClass::NANO : BuffClass::GROUP_NANO;
if(target->addBuff(timeBuffId, ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
if(combatant->addBuff(timeBuffId,
[](EntityRef self, Buff* buff, int status, BuffStack* stack) { [](EntityRef self, Buff* buff, int status, BuffStack* stack) {
Buffs::timeBuffUpdate(self, buff, status, stack); Buffs::timeBuffUpdate(self, buff, status, stack);
}, },
[](EntityRef self, Buff* buff, time_t currTime) { [](EntityRef self, Buff* buff, time_t currTime) {
// no-op // no-op
}, },
&passiveBuff)) affected.push_back(target); &passiveBuff)) affected.push_back(combatant);
} }
return affected; return affected;
@ -308,8 +317,8 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl; std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
) )
ICombatant* plrCombatant = dynamic_cast<ICombatant*>(plr); // TODO ABILITIES
std::vector<ICombatant*> targetData = Abilities::matchTargets(plrCombatant, skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1)); std::vector<ICombatant*> targetData = Abilities::matchTargets(skillData, pkt->iTargetCnt, (int32_t*)(pkt + 1));
Abilities::useNanoSkill(sock, skillData, nano, targetData); Abilities::useNanoSkill(sock, skillData, nano, targetData);
if (plr->Nanos[plr->activeNano].iStamina < 0) if (plr->Nanos[plr->activeNano].iStamina < 0)

View File

@ -80,8 +80,6 @@ struct Player : public Entity, public ICombatant {
uint64_t iFirstUseFlag[2] = {}; uint64_t iFirstUseFlag[2] = {};
time_t lastHeartbeat = 0; time_t lastHeartbeat = 0;
int suspicionRating = 0;
time_t lastShot = 0;
std::vector<sItemBase> buyback = {}; std::vector<sItemBase> buyback = {};
Player() { kind = EntityKind::PLAYER; } Player() { kind = EntityKind::PLAYER; }

View File

@ -23,12 +23,17 @@ using namespace PlayerManager;
std::map<CNSocket*, Player*> PlayerManager::players; std::map<CNSocket*, Player*> PlayerManager::players;
static void addPlayer(CNSocket* key, Player *plr) { static void addPlayer(CNSocket* key, Player& plr) {
players[key] = plr; Player *p = new Player();
plr->chunkPos = Chunking::INVALID_CHUNK;
plr->lastHeartbeat = 0;
std::cout << getPlayerName(plr) << " has joined!" << std::endl; // copy object into heap memory
*p = plr;
players[key] = p;
p->chunkPos = Chunking::INVALID_CHUNK;
p->lastHeartbeat = 0;
std::cout << getPlayerName(p) << " has joined!" << std::endl;
std::cout << players.size() << " players" << std::endl; std::cout << players.size() << " players" << std::endl;
} }
@ -210,72 +215,66 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
return; return;
} }
Player *plr = new Player(); // for convenience
Database::getPlayer(plr, lm->playerId); Player& plr = lm->plr;
// check if account is already in use // check if account is already in use
if (isAccountInUse(plr->accountId)) { if (isAccountInUse(plr.accountId)) {
// kick the other player // kick the other player
exitDuplicate(plr->accountId); exitDuplicate(plr.accountId);
// re-read the player from disk, in case it was just flushed
*plr = {};
Database::getPlayer(plr, lm->playerId);
} }
plr->group = nullptr; response.iID = plr.iID;
response.iID = plr->iID;
response.uiSvrTime = getTime(); response.uiSvrTime = getTime();
response.PCLoadData2CL.iUserLevel = plr->accountLevel; response.PCLoadData2CL.iUserLevel = plr.accountLevel;
response.PCLoadData2CL.iHP = plr->HP; response.PCLoadData2CL.iHP = plr.HP;
response.PCLoadData2CL.iLevel = plr->level; response.PCLoadData2CL.iLevel = plr.level;
response.PCLoadData2CL.iCandy = plr->money; response.PCLoadData2CL.iCandy = plr.money;
response.PCLoadData2CL.iFusionMatter = plr->fusionmatter; response.PCLoadData2CL.iFusionMatter = plr.fusionmatter;
response.PCLoadData2CL.iMentor = plr->mentor; response.PCLoadData2CL.iMentor = plr.mentor;
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
response.PCLoadData2CL.iX = plr->x; response.PCLoadData2CL.iX = plr.x;
response.PCLoadData2CL.iY = plr->y; response.PCLoadData2CL.iY = plr.y;
response.PCLoadData2CL.iZ = plr->z; response.PCLoadData2CL.iZ = plr.z;
response.PCLoadData2CL.iAngle = plr->angle; response.PCLoadData2CL.iAngle = plr.angle;
response.PCLoadData2CL.iBatteryN = plr->batteryN; response.PCLoadData2CL.iBatteryN = plr.batteryN;
response.PCLoadData2CL.iBatteryW = plr->batteryW; response.PCLoadData2CL.iBatteryW = plr.batteryW;
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
response.PCLoadData2CL.iWarpLocationFlag = plr->iWarpLocationFlag; response.PCLoadData2CL.iWarpLocationFlag = plr.iWarpLocationFlag;
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0]; response.PCLoadData2CL.aWyvernLocationFlag[0] = plr.aSkywayLocationFlag[0];
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1]; response.PCLoadData2CL.aWyvernLocationFlag[1] = plr.aSkywayLocationFlag[1];
response.PCLoadData2CL.iActiveNanoSlotNum = -1; response.PCLoadData2CL.iActiveNanoSlotNum = -1;
response.PCLoadData2CL.iFatigue = 50; response.PCLoadData2CL.iFatigue = 50;
response.PCLoadData2CL.PCStyle = plr->PCStyle; response.PCLoadData2CL.PCStyle = plr.PCStyle;
// client doesnt read this, it gets it from charinfo // client doesnt read this, it gets it from charinfo
// response.PCLoadData2CL.PCStyle2 = plr->PCStyle2; // response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
// inventory // inventory
for (int i = 0; i < AEQUIP_COUNT; i++) for (int i = 0; i < AEQUIP_COUNT; i++)
response.PCLoadData2CL.aEquip[i] = plr->Equip[i]; response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
for (int i = 0; i < AINVEN_COUNT; i++) for (int i = 0; i < AINVEN_COUNT; i++)
response.PCLoadData2CL.aInven[i] = plr->Inven[i]; response.PCLoadData2CL.aInven[i] = plr.Inven[i];
// quest inventory // quest inventory
for (int i = 0; i < AQINVEN_COUNT; i++) for (int i = 0; i < AQINVEN_COUNT; i++)
response.PCLoadData2CL.aQInven[i] = plr->QInven[i]; response.PCLoadData2CL.aQInven[i] = plr.QInven[i];
// nanos // nanos
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) { for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
response.PCLoadData2CL.aNanoBank[i] = plr->Nanos[i]; response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
} }
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
response.PCLoadData2CL.aNanoSlots[i] = plr->equippedNanos[i]; response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i];
} }
// missions in progress // missions in progress
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == 0) if (plr.tasks[i] == 0)
break; break;
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr->tasks[i]; response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
TaskData &task = *Missions::Tasks[plr->tasks[i]]; TaskData &task = *Missions::Tasks[plr.tasks[i]];
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j]; response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr->RemainingNPCCount[i][j]; response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j];
/* /*
* client doesn't care about NeededItem ID and Count, * client doesn't care about NeededItem ID and Count,
* it gets Count from Quest Inventory * it gets Count from Quest Inventory
@ -285,12 +284,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
*/ */
} }
} }
response.PCLoadData2CL.iCurrentMissionID = plr->CurrentMissionID; response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
// completed missions // completed missions
// the packet requires 32 items, but the client only checks the first 16 (shrug) // the packet requires 32 items, but the client only checks the first 16 (shrug)
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
response.PCLoadData2CL.aQuestFlag[i] = plr->aQuestFlag[i]; response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
} }
// Computress tips // Computress tips
@ -299,11 +298,11 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX; response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
} }
else { else {
response.PCLoadData2CL.iFirstUseFlag1 = plr->iFirstUseFlag[0]; response.PCLoadData2CL.iFirstUseFlag1 = plr.iFirstUseFlag[0];
response.PCLoadData2CL.iFirstUseFlag2 = plr->iFirstUseFlag[1]; response.PCLoadData2CL.iFirstUseFlag2 = plr.iFirstUseFlag[1];
} }
plr->instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1)); sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
sock->setFEKey(lm->FEKey); sock->setFEKey(lm->FEKey);
@ -314,14 +313,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
// transmit MOTD after entering the game, so the client hopefully changes modes on time // transmit MOTD after entering the game, so the client hopefully changes modes on time
Chat::sendServerMessage(sock, settings::MOTDSTRING); Chat::sendServerMessage(sock, settings::MOTDSTRING);
// transfer ownership of Player object into the shard (still valid in this function though) // copy Player object into the shard
addPlayer(sock, plr); addPlayer(sock, plr);
// check if there is an expiring vehicle // check if there is an expiring vehicle
Items::checkItemExpire(sock, plr); Items::checkItemExpire(sock, getPlayer(sock));
// set player equip stats // set player equip stats
Items::setItemStats(plr); Items::setItemStats(getPlayer(sock));
Missions::failInstancedMissions(sock); Missions::failInstancedMissions(sock);
@ -332,9 +331,9 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
for (auto& pair : players) for (auto& pair : players)
if (pair.second->notify) if (pair.second->notify)
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(plr) + " has joined."); Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined.");
// deallocate lm // deallocate lm (and therefore the plr object)
delete lm; delete lm;
} }
@ -401,11 +400,12 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
if (!(plr->hasBuff(ECSB_PHOENIX))) if (!(plr->hasBuff(ECSB_PHOENIX)))
return; // sanity check return; // sanity check
plr->Nanos[plr->activeNano].iStamina = 0; plr->Nanos[plr->activeNano].iStamina = 0;
// TODO ABILITIES
//Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
// fallthrough // fallthrough
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
plr->HP = PC_MAXHEALTH(plr->level) / 2; plr->HP = PC_MAXHEALTH(plr->level) / 2;
break; break;
default: // plain respawn default: // plain respawn
plr->HP = PC_MAXHEALTH(plr->level) / 2; plr->HP = PC_MAXHEALTH(plr->level) / 2;
// fallthrough // fallthrough

View File

@ -33,22 +33,6 @@ public:
const char *what() const throw() { return msg.c_str(); } const char *what() const throw() { return msg.c_str(); }
}; };
/*
* We must refuse to run if an invalid NPC type is found in the JSONs, especially
* the gruntwork file. If we were to just skip loading invalid NPCs, they would get
* silently dropped from the gruntwork file, which would be confusing in situations
* where a gruntwork file for the wrong game build was accidentally loaded.
*/
static void ensureValidNPCType(int type, std::string filename) {
// last known NPC type
int npcLimit = NPCManager::NPCData.back()["m_iNpcNumber"];
if (type > npcLimit) {
std::cout << "[FATAL] " << filename << " contains an invalid NPC type: " << type << std::endl;
exit(1);
}
}
/* /*
* Create a full and properly-paced path by interpolating between keyframes. * Create a full and properly-paced path by interpolating between keyframes.
*/ */
@ -690,8 +674,6 @@ static void loadEggs(json& eggData, int32_t* nextId) {
* Load gruntwork output, if it exists * Load gruntwork output, if it exists
*/ */
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) { static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
if (gruntwork.is_null())
return;
try { try {
auto paths = gruntwork["paths"]; auto paths = gruntwork["paths"];
@ -741,8 +723,8 @@ static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
} }
static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
if (gruntwork.is_null())
return; if (gruntwork.is_null()) return;
try { try {
// skyway paths // skyway paths
@ -794,8 +776,6 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
int id = (*nextId)--; int id = (*nextId)--;
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"]; uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
ensureValidNPCType((int)mob["iNPCType"], settings::GRUNTWORKJSON);
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) { if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"], npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
NPCManager::NPCData[(int)mob["iNPCType"]], id); NPCManager::NPCData[(int)mob["iNPCType"]], id);
@ -815,9 +795,6 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
auto groups = gruntwork["groups"]; auto groups = gruntwork["groups"];
for (auto _group = groups.begin(); _group != groups.end(); _group++) { for (auto _group = groups.begin(); _group != groups.end(); _group++) {
auto leader = _group.value(); auto leader = _group.value();
ensureValidNPCType((int)leader["iNPCType"], settings::GRUNTWORKJSON);
auto td = NPCManager::NPCData[(int)leader["iNPCType"]]; auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"]; uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
@ -838,9 +815,6 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
int followerCount = 0; int followerCount = 0;
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) { for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
auto follower = _fol.value(); auto follower = _fol.value();
ensureValidNPCType((int)follower["iNPCType"], settings::GRUNTWORKJSON);
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]]; auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId); Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
@ -897,9 +871,10 @@ static void loadNPCs(json& npcData) {
npcID += NPC_ID_OFFSET; npcID += NPC_ID_OFFSET;
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"]; int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
int type = (int)npc["iNPCType"]; int type = (int)npc["iNPCType"];
if (NPCManager::NPCData[type].is_null()) {
ensureValidNPCType(type, settings::NPCJSON); std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
continue;
}
#ifdef ACADEMY #ifdef ACADEMY
// do not spawn NPCs in the future // do not spawn NPCs in the future
if (npc["iX"] > 512000 && npc["iY"] < 256000) if (npc["iX"] > 512000 && npc["iY"] < 256000)
@ -941,9 +916,10 @@ static void loadMobs(json& npcData, int32_t* nextId) {
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
npcID += MOB_ID_OFFSET; npcID += MOB_ID_OFFSET;
int type = (int)npc["iNPCType"]; int type = (int)npc["iNPCType"];
if (NPCManager::NPCData[type].is_null()) {
ensureValidNPCType(type, settings::MOBJSON); std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
continue;
}
auto td = NPCManager::NPCData[type]; auto td = NPCManager::NPCData[type];
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"]; uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
@ -971,10 +947,7 @@ static void loadMobs(json& npcData, int32_t* nextId) {
for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) { for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
auto leader = _group.value(); auto leader = _group.value();
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
leadID += MOB_GROUP_ID_OFFSET; leadID += MOB_GROUP_ID_OFFSET;
ensureValidNPCType(leader["iNPCType"], settings::MOBJSON);
auto td = NPCManager::NPCData[(int)leader["iNPCType"]]; auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"]; uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
auto followers = leader["aFollowers"]; auto followers = leader["aFollowers"];
@ -1004,9 +977,6 @@ static void loadMobs(json& npcData, int32_t* nextId) {
int followerCount = 0; int followerCount = 0;
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) { for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
auto follower = _fol.value(); auto follower = _fol.value();
ensureValidNPCType(follower["iNPCType"], settings::MOBJSON);
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]]; auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId); Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
@ -1112,39 +1082,19 @@ void TableData::init() {
}; };
// load JSON data into tables // load JSON data into tables
std::ifstream fstream;
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
std::pair<json*, std::string>& table = tables[i]; std::pair<json*, std::string>& table = tables[i];
// scope for fstream
{
std::ifstream fstream;
fstream.open(settings::TDATADIR + "/" + table.second); // open file fstream.open(settings::TDATADIR + "/" + table.second); // open file
if (!fstream.fail()) {
// did we fail to open the file? fstream >> *table.first; // load file contents into table
if (fstream.fail()) { } else {
// gruntwork isn't critical if (table.first != &gruntwork) { // gruntwork isn't critical
if (table.first == &gruntwork)
continue;
std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl; std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl;
exit(1); exit(1);
} }
// is the file empty?
if (fstream.peek() == std::ifstream::traits_type::eof()) {
// tolerate empty gruntwork file
if (table.first == &gruntwork) {
std::cout << "[WARN] The gruntwork file is empty" << std::endl;
continue;
}
std::cerr << "[FATAL] Critical tdata file is empty: " << table.second << std::endl;
exit(1);
}
// load file contents into table
fstream >> *table.first;
} }
fstream.close();
// patching: load each patch directory specified in the config file // patching: load each patch directory specified in the config file
@ -1159,11 +1109,11 @@ void TableData::init() {
std::string patchModuleName = *it; std::string patchModuleName = *it;
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second; std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;
try { try {
std::ifstream fstream;
fstream.open(patchFile); fstream.open(patchFile);
fstream >> patch; // load into temporary json object fstream >> patch; // load into temporary json object
std::cout << "[INFO] Patching " << patchFile << std::endl; std::cout << "[INFO] Patching " << patchFile << std::endl;
patchJSON(table.first, &patch); // patch patchJSON(table.first, &patch); // patch
fstream.close();
} catch (const std::exception& err) { } catch (const std::exception& err) {
// no-op // no-op
} }

View File

@ -3,7 +3,6 @@
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "db/Database.hpp"
using namespace Trading; using namespace Trading;
@ -273,8 +272,6 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
return; return;
} }
Database::commitTrade(plr, plr2);
} }
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) { static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {

View File

@ -6,9 +6,6 @@
#include "Items.hpp" #include "Items.hpp"
#include "Rand.hpp" #include "Rand.hpp"
// 7 days
#define VEHICLE_EXPIRY_DURATION 604800
using namespace Vendors; using namespace Vendors;
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables; std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
@ -61,8 +58,8 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
// if vehicle // if vehicle
if (req->Item.iType == 10) { if (req->Item.iType == 10) {
// set time limit: current time + expiry duration // set time limit: current time + 7days
req->Item.iTimeLimit = getTimestamp() + VEHICLE_EXPIRY_DURATION; req->Item.iTimeLimit = getTimestamp() + 604800;
} }
if (slot != req->iInvenSlotNum) { if (slot != req->iInvenSlotNum) {
@ -232,20 +229,12 @@ static void vendorTable(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
sItemBase base = {}; sItemBase base;
base.iID = listings[i].id; base.iID = listings[i].id;
base.iOpt = 0;
base.iTimeLimit = 0;
base.iType = listings[i].type; base.iType = listings[i].type;
/*
* Set vehicle expiry value.
*
* Note: sItemBase.iTimeLimit in the context of vendor listings contains
* a duration, unlike in most other contexts where it contains the
* expiration timestamp.
*/
if (listings[i].type == 10)
base.iTimeLimit = VEHICLE_EXPIRY_DURATION;
sItemVendor vItem; sItemVendor vItem;
vItem.item = base; vItem.item = base;
vItem.iSortNum = listings[i].sort; vItem.iSortNum = listings[i].sort;

View File

@ -41,8 +41,7 @@ int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) { uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
uint64_t num = (uint64_t)(iv1 + 1); uint64_t num = (uint64_t)(iv1 + 1);
uint64_t num2 = (uint64_t)(iv2 + 1); uint64_t num2 = (uint64_t)(iv2 + 1);
uint64_t dEKey; uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
memcpy(&dEKey, defaultKey, sizeof(dEKey));
return dEKey * (uTime * num * num2); return dEKey * (uTime * num * num2);
} }
@ -66,7 +65,7 @@ CNPacketData::CNPacketData(void *b, uint32_t t, int l, int trnum, void *trs):
// ========================================================[[ CNSocket ]]======================================================== // ========================================================[[ CNSocket ]]========================================================
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) { CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
memcpy(&EKey, CNSocketEncryption::defaultKey, sizeof(EKey)); EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
} }
bool CNSocket::sendData(uint8_t* data, int size) { bool CNSocket::sendData(uint8_t* data, int size) {
@ -110,11 +109,7 @@ bool CNSocket::isAlive() {
} }
void CNSocket::kill() { void CNSocket::kill() {
if (!alive)
return;
alive = false; alive = false;
#ifdef _WIN32 #ifdef _WIN32
shutdown(sock, SD_BOTH); shutdown(sock, SD_BOTH);
closesocket(sock); closesocket(sock);
@ -246,10 +241,9 @@ void CNSocket::step() {
if (readSize <= 0) { if (readSize <= 0) {
// we aren't reading a packet yet, try to start looking for one // we aren't reading a packet yet, try to start looking for one
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0); int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
if (recved >= 0 && recved < sizeof(int32_t)) { if (recved == 0) {
// too little data for readSize or the socket was closed normally (when 0 bytes were read) // the socket was closed normally
kill(); kill();
return;
} else if (!SOCKETERROR(recved)) { } else if (!SOCKETERROR(recved)) {
// we got our packet size!!!! // we got our packet size!!!!
readSize = *((int32_t*)readBuffer); readSize = *((int32_t*)readBuffer);
@ -270,12 +264,11 @@ void CNSocket::step() {
} }
if (readSize > 0 && readBufferIndex < readSize) { if (readSize > 0 && readBufferIndex < readSize) {
// read until the end of the packet (or at least try to) // read until the end of the packet! (or at least try too)
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0); int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
if (recved == 0) { if (recved == 0) {
// the socket was closed normally // the socket was closed normally
kill(); kill();
return;
} else if (!SOCKETERROR(recved)) } else if (!SOCKETERROR(recved))
readBufferIndex += recved; readBufferIndex += recved;
else if (OF_ERRNO != OF_EWOULD) { else if (OF_ERRNO != OF_EWOULD) {
@ -418,9 +411,9 @@ void CNServer::addPollFD(SOCKET s) {
fds.push_back({s, POLLIN}); fds.push_back({s, POLLIN});
} }
void CNServer::removePollFD(int fd) { void CNServer::removePollFD(int i) {
auto it = fds.begin(); auto it = fds.begin();
while (it != fds.end() && it->fd != fd) while (it != fds.end() && it->fd != fds[i].fd)
it++; it++;
assert(it != fds.end()); assert(it != fds.end());
@ -465,7 +458,7 @@ void CNServer::start() {
if (!setSockNonblocking(sock, newConnectionSocket)) if (!setSockNonblocking(sock, newConnectionSocket))
continue; continue;
std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl; std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
addPollFD(newConnectionSocket); addPollFD(newConnectionSocket);
@ -480,15 +473,10 @@ void CNServer::start() {
} else { } else {
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
// halt packet handling if server is shutting down
if (!active)
return;
// player sockets // player sockets
if (connections.find(fds[i].fd) == connections.end()) { if (connections.find(fds[i].fd) == connections.end()) {
std::cout << "[FATAL] Event on non-existent socket: " << fds[i].fd << std::endl; std::cout << "[WARN] Event on non-existant socket?" << std::endl;
assert(0); continue; // just to be safe
/* not reached */
} }
CNSocket* cSock = connections[fds[i].fd]; CNSocket* cSock = connections[fds[i].fd];
@ -497,29 +485,22 @@ void CNServer::start() {
if (fds[i].revents & ~POLLIN) if (fds[i].revents & ~POLLIN)
cSock->kill(); cSock->kill();
if (cSock->isAlive()) if (cSock->isAlive()) {
cSock->step(); cSock->step();
} else {
killConnection(cSock);
connections.erase(fds[i].fd);
delete cSock;
removePollFD(i);
// a new entry was moved to this position, so we check it again
i--;
}
} }
} }
onStep(); onStep();
// clean up dead connection sockets
auto it = connections.begin();
while (it != connections.end()) {
CNSocket *cSock = it->second;
if (!cSock->isAlive()) {
killConnection(cSock);
it = connections.erase(it);
removePollFD(cSock->sock);
delete cSock;
} else {
it++;
}
}
} }
} }

View File

@ -230,7 +230,6 @@ protected:
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
std::vector<PollFD> fds; std::vector<PollFD> fds;
std::string serverType = "invalid";
SOCKET sock; SOCKET sock;
uint16_t port; uint16_t port;
socklen_t addressSize; socklen_t addressSize;

View File

@ -32,7 +32,7 @@ void CNShared::pruneLoginMetadata(CNServer *serv, time_t currTime) {
auto& sk = it->first; auto& sk = it->first;
auto& lm = it->second; auto& lm = it->second;
if (currTime > lm->timestamp + CNSHARED_TIMEOUT) { if (lm->timestamp + CNSHARED_TIMEOUT > currTime) {
std::cout << "[WARN] Pruning hung connection attempt" << std::endl; std::cout << "[WARN] Pruning hung connection attempt" << std::endl;
// deallocate object and remove map entry // deallocate object and remove map entry

View File

@ -11,14 +11,14 @@
#include "Player.hpp" #include "Player.hpp"
/* /*
* Connecions time out after 5 minutes, checked every 30 seconds. * Connecions time out after 15 minutes, checked every 30 seconds.
*/ */
#define CNSHARED_TIMEOUT 300000 #define CNSHARED_TIMEOUT 900000
#define CNSHARED_PERIOD 30000 #define CNSHARED_PERIOD 30000
struct LoginMetadata { struct LoginMetadata {
uint64_t FEKey; uint64_t FEKey;
int32_t playerId; Player plr;
time_t timestamp; time_t timestamp;
}; };

View File

@ -41,7 +41,6 @@ namespace Database {
uint64_t Timestamp; uint64_t Timestamp;
}; };
void init();
void open(); void open();
void close(); void close();
@ -53,8 +52,7 @@ namespace Database {
bool banPlayer(int playerId, std::string& reason); bool banPlayer(int playerId, std::string& reason);
bool unbanPlayer(int playerId); bool unbanPlayer(int playerId);
void updateSelected(int accountId, int slot); void updateSelected(int accountId, int playerId);
void updateSelectedByPlayerId(int accountId, int playerId);
bool validateCharacter(int characterID, int userID); bool validateCharacter(int characterID, int userID);
bool isNameFree(std::string firstName, std::string lastName); bool isNameFree(std::string firstName, std::string lastName);
@ -80,9 +78,7 @@ namespace Database {
// getting players // getting players
void getPlayer(Player* plr, int id); void getPlayer(Player* plr, int id);
bool _updatePlayer(Player *player);
void updatePlayer(Player *player); void updatePlayer(Player *player);
void commitTrade(Player *plr1, Player *plr2);
// buddies // buddies
int getNumBuddies(Player* player); int getNumBuddies(Player* player);
@ -102,7 +98,7 @@ namespace Database {
void deleteEmailAttachments(int playerID, int index, int slot); void deleteEmailAttachments(int playerID, int index, int slot);
void deleteEmails(int playerID, int64_t* indices); void deleteEmails(int playerID, int64_t* indices);
int getNextEmailIndex(int playerID); int getNextEmailIndex(int playerID);
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender); bool sendEmail(EmailData* data, std::vector<sItemBase> attachments);
// racing // racing
RaceRanking getTopRaceRanking(int epID, int playerID); RaceRanking getTopRaceRanking(int epID, int playerID);

View File

@ -152,8 +152,7 @@ void Database::updateEmailContent(EmailData* data) {
sqlite3_step(stmt); sqlite3_step(stmt);
int attachmentsCount = sqlite3_column_int(stmt, 0); int attachmentsCount = sqlite3_column_int(stmt, 0);
// set attachment flag dynamically data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0;
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@ -266,7 +265,7 @@ int Database::getNextEmailIndex(int playerID) {
return (index > 0 ? index + 1 : 1); return (index > 0 ? index + 1 : 1);
} }
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender) { bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
@ -331,13 +330,6 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Pl
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
if (!_updatePlayer(sender)) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return false;
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
return true; return true;
} }

View File

@ -236,20 +236,7 @@ static int getTableSize(std::string tableName) {
return result; return result;
} }
void Database::init() {
std::cout << "[INFO] Built with libsqlite " SQLITE_VERSION << std::endl;
if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER)
std::cout << "[INFO] Using libsqlite " << std::string(sqlite3_libversion()) << std::endl;
if (sqlite3_libversion_number() < MIN_SUPPORTED_SQLITE_NUMBER) {
std::cerr << "[FATAL] Runtime sqlite version too old. Minimum compatible version: " MIN_SUPPORTED_SQLITE << std::endl;
exit(1);
}
}
void Database::open() { void Database::open() {
// XXX: move locks here // XXX: move locks here
int rc = sqlite3_open(settings::DBPATH.c_str(), &db); int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {

View File

@ -3,15 +3,6 @@
#include "db/Database.hpp" #include "db/Database.hpp"
#include <sqlite3.h> #include <sqlite3.h>
#define MIN_SUPPORTED_SQLITE_NUMBER 3033000
#define MIN_SUPPORTED_SQLITE "3.33.0"
// we can't use this in #error, since it doesn't expand macros
// Compile-time libsqlite version check
#if SQLITE_VERSION_NUMBER < MIN_SUPPORTED_SQLITE_NUMBER
#error libsqlite version too old. Minimum compatible version: 3.33.0
#endif
extern std::mutex dbCrit; extern std::mutex dbCrit;
extern sqlite3 *db; extern sqlite3 *db;

View File

@ -79,29 +79,6 @@ void Database::updateSelected(int accountId, int slot) {
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl; std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
} }
void Database::updateSelectedByPlayerId(int accountId, int32_t playerId) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
UPDATE Accounts SET
Selected = p.Slot,
LastLogin = (strftime('%s', 'now'))
FROM (SELECT Slot From Players WHERE PlayerId = ?) AS p
WHERE AccountID = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
sqlite3_bind_int(stmt, 2, accountId);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
std::cout << "[WARN] Database fail on updateSelectedByPlayerId(): " << sqlite3_errmsg(db) << std::endl;
}
bool Database::validateCharacter(int characterID, int userID) { bool Database::validateCharacter(int characterID, int userID) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);

View File

@ -285,13 +285,11 @@ void Database::getPlayer(Player* plr, int id) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
/* void Database::updatePlayer(Player *player) {
* Low-level function to save a player to DB. std::lock_guard<std::mutex> lock(dbCrit);
* Must be run in a SQL transaction and with dbCrit locked.
* The caller manages the transacstion, so if this function returns false, sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
* the caller must roll it back.
*/
bool Database::_updatePlayer(Player *player) {
const char* sql = R"( const char* sql = R"(
UPDATE Players UPDATE Players
SET SET
@ -338,8 +336,10 @@ bool Database::_updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 21, player->iID); sqlite3_bind_int(stmt, 21, player->iID);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return false; sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
} }
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@ -375,8 +375,10 @@ bool Database::_updatePlayer(Player *player) {
rc = sqlite3_step(stmt); rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) { if (rc != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -393,8 +395,10 @@ bool Database::_updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit); sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -411,8 +415,10 @@ bool Database::_updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit); sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -445,8 +451,10 @@ bool Database::_updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 4, player->QInven[i].iID); sqlite3_bind_int(stmt, 4, player->QInven[i].iID);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -479,8 +487,10 @@ bool Database::_updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina); sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -514,47 +524,14 @@ bool Database::_updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]); sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return false; return;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return true;
}
void Database::updatePlayer(Player *player) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
if (!_updatePlayer(player)) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
}
void Database::commitTrade(Player *plr1, Player *plr2) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
if (!_updatePlayer(plr1)) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
}
if (!_updatePlayer(plr2)) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
} }

View File

@ -98,9 +98,6 @@ void initsignals() {
} }
int main() { int main() {
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
#ifdef _WIN32 #ifdef _WIN32
WSADATA wsaData; WSADATA wsaData;
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) { if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
@ -108,15 +105,15 @@ int main() {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
#endif #endif
initsignals(); initsignals();
settings::init(); settings::init();
Database::init();
Rand::init(getTime());
TableData::init();
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
std::cout << "[INFO] Intializing Packet Managers..." << std::endl; std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
Rand::init(getTime());
TableData::init();
PlayerManager::init(); PlayerManager::init();
PlayerMovement::init(); PlayerMovement::init();
BuiltinCommands::init(); BuiltinCommands::init();
@ -135,9 +132,8 @@ int main() {
Email::init(); Email::init();
Groups::init(); Groups::init();
Racing::init(); Racing::init();
Trading::init();
Database::open(); Database::open();
Trading::init();
switch (settings::EVENTMODE) { switch (settings::EVENTMODE) {
case 0: break; // no event case 0: break; // no event

View File

@ -153,18 +153,15 @@ static sock_filter filter[] = {
ALLOW_SYSCALL(read), ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write), ALLOW_SYSCALL(write),
ALLOW_SYSCALL(close), ALLOW_SYSCALL(close),
#ifdef __NR_stat #if __NR_stat
ALLOW_SYSCALL(stat), ALLOW_SYSCALL(stat),
#endif #endif
ALLOW_SYSCALL(fstat), ALLOW_SYSCALL(fstat),
#ifdef __NR_newfstatat
ALLOW_SYSCALL(newfstatat),
#endif
ALLOW_SYSCALL(fsync), // maybe ALLOW_SYSCALL(fsync), // maybe
#ifdef __NR_creat #if __NR_creat
ALLOW_SYSCALL(creat), // maybe; for DB journal ALLOW_SYSCALL(creat), // maybe; for DB journal
#endif #endif
#ifdef __NR_unlink #if __NR_unlink
ALLOW_SYSCALL(unlink), // for DB journal ALLOW_SYSCALL(unlink), // for DB journal
#endif #endif
ALLOW_SYSCALL(lseek), // musl-libc; alt DB ALLOW_SYSCALL(lseek), // musl-libc; alt DB
@ -195,9 +192,6 @@ static sock_filter filter[] = {
ALLOW_SYSCALL(exit_group), ALLOW_SYSCALL(exit_group),
ALLOW_SYSCALL(rt_sigprocmask), // musl-libc ALLOW_SYSCALL(rt_sigprocmask), // musl-libc
ALLOW_SYSCALL(clock_nanosleep), // gets called very rarely ALLOW_SYSCALL(clock_nanosleep), // gets called very rarely
#ifdef __NR_rseq
ALLOW_SYSCALL(rseq),
#endif
// to crash properly on SIGSEGV // to crash properly on SIGSEGV
DENY_SYSCALL_ERRNO(tgkill, EPERM), DENY_SYSCALL_ERRNO(tgkill, EPERM),
@ -280,7 +274,7 @@ static sock_filter filter[] = {
#endif #endif
// AArch64 (ARM64) // AArch64 (ARM64)
#ifdef __NR_unlinkat #if __NR_unlinkat
ALLOW_SYSCALL(unlinkat), ALLOW_SYSCALL(unlinkat),
#endif #endif
#ifdef __NR_fstatat64 #ifdef __NR_fstatat64

View File

@ -13,7 +13,6 @@
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
CNLoginServer::CNLoginServer(uint16_t p) { CNLoginServer::CNLoginServer(uint16_t p) {
serverType = "login";
port = p; port = p;
pHandler = &CNLoginServer::handlePacket; pHandler = &CNLoginServer::handlePacket;
init(); init();
@ -211,12 +210,9 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
// send the resp in with original key // send the resp in with original key
sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC); sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC);
uint64_t defaultKey;
memcpy(&defaultKey, CNSocketEncryption::defaultKey, sizeof(defaultKey));
// update keys // update keys
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1)); sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
sock->setFEKey(CNSocketEncryption::createNewKey(defaultKey, login->iClientVerC, 1)); sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
DEBUGLOG( DEBUGLOG(
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl; std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
@ -477,7 +473,11 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
LoginMetadata *lm = new LoginMetadata(); LoginMetadata *lm = new LoginMetadata();
lm->FEKey = sock->getFEKey(); lm->FEKey = sock->getFEKey();
lm->timestamp = getTime(); lm->timestamp = getTime();
lm->playerId = selection->iPC_UID;
Database::getPlayer(&lm->plr, selection->iPC_UID);
// this should never happen but for extra safety
if (lm->plr.iID == 0)
return invalidCharacter(sock);
resp.iEnterSerialKey = Rand::cryptoRand(); resp.iEnterSerialKey = Rand::cryptoRand();
@ -487,7 +487,7 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC); sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC);
// update current slot in DB // update current slot in DB
Database::updateSelectedByPlayerId(loginSessions[sock].userID, selection->iPC_UID); Database::updateSelected(loginSessions[sock].userID, lm->plr.slot);
} }
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) { void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {

View File

@ -18,7 +18,6 @@ std::map<uint32_t, PacketHandler> CNShardServer::ShardPackets;
std::list<TimerEvent> CNShardServer::Timers; std::list<TimerEvent> CNShardServer::Timers;
CNShardServer::CNShardServer(uint16_t p) { CNShardServer::CNShardServer(uint16_t p) {
serverType = "shard";
port = p; port = p;
pHandler = &CNShardServer::handlePacket; pHandler = &CNShardServer::handlePacket;
REGISTER_SHARD_TIMER(keepAliveTimer, 4000); REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
@ -118,10 +117,6 @@ void CNShardServer::kill() {
void CNShardServer::onStep() { void CNShardServer::onStep() {
time_t currTime = getTime(); time_t currTime = getTime();
// do not evaluate timers if the server is shutting down
if (!active)
return;
for (TimerEvent& event : Timers) { for (TimerEvent& event : Timers) {
if (event.scheduledEvent == 0) { if (event.scheduledEvent == 0) {
// event hasn't been queued yet, go ahead and do that // event hasn't been queued yet, go ahead and do that

View File

@ -21,7 +21,6 @@ bool settings::LOCALHOSTWORKAROUND = true;
time_t settings::TIMEOUT = 60000; time_t settings::TIMEOUT = 60000;
int settings::VIEWDISTANCE = 25600; int settings::VIEWDISTANCE = 25600;
bool settings::SIMULATEMOBS = true; bool settings::SIMULATEMOBS = true;
bool settings::ANTICHEAT = true;
// default spawn point // default spawn point
#ifndef ACADEMY #ifndef ACADEMY
@ -110,7 +109,6 @@ void settings::init() {
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL); ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE); EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG); DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED); MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT); MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL); MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);

View File

@ -12,7 +12,6 @@ namespace settings {
extern int SHARDPORT; extern int SHARDPORT;
extern std::string SHARDSERVERIP; extern std::string SHARDSERVERIP;
extern bool LOCALHOSTWORKAROUND; extern bool LOCALHOSTWORKAROUND;
extern bool ANTICHEAT;
extern time_t TIMEOUT; extern time_t TIMEOUT;
extern int VIEWDISTANCE; extern int VIEWDISTANCE;
extern bool SIMULATEMOBS; extern bool SIMULATEMOBS;

2
tdata

@ -1 +1 @@
Subproject commit cc65dbb402b5baa2b604ed66132edd88cc82a52a Subproject commit 8230fb8649d6da8de71e918c24efbb55d1c08a88