2 Commits

Author SHA1 Message Date
234ec46f36 Move timingSafeStrcmp to main 2024-11-16 19:16:49 -08:00
4d0553e9ef Refactor login packet handler for more flexible auth 2024-11-16 13:42:20 -08:00
48 changed files with 359 additions and 1093 deletions

View File

@@ -8,7 +8,6 @@ on:
- .github/workflows/check-builds.yaml - .github/workflows/check-builds.yaml
- CMakeLists.txt - CMakeLists.txt
- Makefile - Makefile
- tdata
pull_request: pull_request:
types: [opened, reopened, synchronize, ready_for_review] types: [opened, reopened, synchronize, ready_for_review]
paths: paths:
@@ -20,7 +19,7 @@ on:
jobs: jobs:
ubuntu-build: ubuntu-build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- name: Set environment - name: Set environment
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
@@ -49,7 +48,6 @@ jobs:
Rename-Item -Path "bin/fusion" -newName "$version-fusion" Rename-Item -Path "bin/fusion" -newName "$version-fusion"
Write-Output "Built version $version" Write-Output "Built version $version"
} }
Copy-Item -Path "tdata" -Destination "bin/tdata" -Recurse
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
Copy-Item -Path "config.ini" -Destination "bin" Copy-Item -Path "config.ini" -Destination "bin"
shell: pwsh shell: pwsh
@@ -60,7 +58,7 @@ jobs:
path: bin path: bin
windows-build: windows-build:
runs-on: windows-2022 runs-on: windows-2019
steps: steps:
- name: Set environment - name: Set environment
run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV
@@ -75,7 +73,7 @@ jobs:
$configurations = "Release" $configurations = "Release"
# "Debug" builds are disabled, since we don't really need them # "Debug" builds are disabled, since we don't really need them
$vsPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise" $vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise"
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
@@ -102,7 +100,6 @@ jobs:
} }
Rename-Item -Path "bin/$configuration" -newName "$version-$configuration" Rename-Item -Path "bin/$configuration" -newName "$version-$configuration"
Write-Output "Built version $version $configuration" Write-Output "Built version $version $configuration"
Copy-Item -Path "tdata" -Destination "bin/$version-$configuration/tdata" -Recurse
Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse
Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration" Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration"
} }
@@ -111,11 +108,11 @@ jobs:
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: 'windows-vs2022-bin-x64-${{ env.SHORT_SHA }}' name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}'
path: bin path: bin
copy-artifacts: copy-artifacts:
if: github.event_name != 'pull_request' && (github.ref_type == 'tag' || github.ref_name == 'master') if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [windows-build, ubuntu-build] needs: [windows-build, ubuntu-build]
env: env:
@@ -124,6 +121,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: recursive
fetch-depth: 0 fetch-depth: 0
- run: | - run: |
GITDESC=$(git describe --tags) GITDESC=$(git describe --tags)

View File

@@ -38,4 +38,4 @@ EXPOSE 23000/tcp
EXPOSE 23001/tcp EXPOSE 23001/tcp
EXPOSE 8003/tcp EXPOSE 8003/tcp
LABEL Name=openfusion Version=2.0.0 LABEL Name=openfusion Version=1.6.0

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2025 OpenFusion Contributors Copyright (c) 2020-2024 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

@@ -14,22 +14,22 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
### Getting Started ### Getting Started
#### Method A: Installer (Easiest) #### Method A: Installer (Easiest)
1. Download the launcher installer by clicking [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Installer.exe) - choose to run the file. 1. Download the client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.6/OpenFusionClient-1.6-Installer.exe) - choose to run the file.
2. After a few moments, the launcher should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect. 2. After a few moments, the client should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
3. 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. 3. 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. 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 3. 4. 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 3.
#### Method B: Standalone .zip file #### Method B: Standalone .zip file
1. Download the launcher from [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Portable.zip). 1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.6/OpenFusionClient-1.6.zip).
2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install. 2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install.
3. Run OpenFusionLauncher.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect. 3. Run OpenFusionClient.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
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://openfusion.dev/docs/guides/running-on-linux/). Instructions for getting the client to run on Linux through Wine can be found [here](https://openfusion.dev/docs/guides/running-on-linux/).
### Hosting a server ### Hosting a server
1. Grab `OpenFusionServer-Windows-Original.zip` or `OpenFusionServer-Windows-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/latest). 1. Grab `OpenFusionServer-1.6-Original.zip` or `OpenFusionServer-1.6-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.6).
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server. 2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
3. Add a new server to the client's list: 3. Add a new server to the client's list:
1. For Description, enter anything you want. This is what will show up in the server list. 1. For Description, enter anything you want. This is what will show up in the server list.

View File

@@ -12,8 +12,6 @@ sandbox=true
[login] [login]
# must be kept in sync with loginInfo.php # must be kept in sync with loginInfo.php
port=23000 port=23000
# will all name wheel names be approved instantly?
acceptallwheelnames=true
# will all custom names be approved instantly? # will all custom names be approved instantly?
acceptallcustomnames=true acceptallcustomnames=true
# should attempts to log into non-existent accounts # should attempts to log into non-existent accounts
@@ -43,33 +41,6 @@ simulatemobs=true
# little message players see when they enter the game # little message players see when they enter the game
motd=Welcome to OpenFusion! motd=Welcome to OpenFusion!
# Should drop fixes be enabled?
# This will add drops to (mostly Academy-specific) mobs that don't have drops
# and rearrange drop tables that are either unassigned or stranded in difficult to reach mobs
# e.g. Hyper Fusionfly and Fusion Numbuh Four drops will become more accessible.
# This is a polish option that is slightly inauthentic to the original game.
#dropfixesenabled=true
# Should groups have to divide up gained Taros / FM among themselves?
# Taros is divided up, FM gets diminished per group member, roughly -12.5% per group member
# Original game worked like this. Uncomment below to disable this behavior.
#lesstarofmingroupdisabled=true
# General reward percentages
# You can change the rate of taro and fusion matter gains for all players.
# The numbers are in percentages, i.e. 1000 is 1000%. You should only use whole numbers, no decimals.
# Uncomment and change below to your desired rate. Defaults are 100%, regular gain rates.
#tarorate=100
#fusionmatterrate=100
# Should expired items in the bank disappear automatically?
# Original game let you kep expired items in the bank until you take them out.
# Uncomment below to enable this behavior.
#removeexpireditemsfrombank=true
# Should there be a score cap for infected zone races?
#izracescorecapped=true
# The following are the default locations of the JSON files the server # The following are the default locations of the JSON files the server
# requires to run. You can override them by changing their values and # requires to run. You can override them by changing their values and
# uncommenting them (removing the leading # character from that line). # uncommenting them (removing the leading # character from that line).
@@ -99,6 +70,9 @@ motd=Welcome to OpenFusion!
# location of the database # location of the database
#dbpath=database.db #dbpath=database.db
# should there be a score cap for infected zone races?
#izracescorecapped=true
# should tutorial flags be disabled off the bat? # should tutorial flags be disabled off the bat?
disablefirstuseflag=true disablefirstuseflag=true

View File

@@ -1,8 +0,0 @@
BEGIN TRANSACTION;
-- New Columns
ALTER TABLE Accounts ADD Email TEXT DEFAULT '' NOT NULL;
ALTER TABLE Accounts ADD LastPasswordReset INTEGER DEFAULT 0 NOT NULL;
-- Update DB Version
UPDATE Meta SET Value = 6 WHERE Key = 'DatabaseVersion';
UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration';
COMMIT;

View File

@@ -9,8 +9,6 @@ CREATE TABLE IF NOT EXISTS Accounts (
BannedUntil INTEGER DEFAULT 0 NOT NULL, BannedUntil INTEGER DEFAULT 0 NOT NULL,
BannedSince INTEGER DEFAULT 0 NOT NULL, BannedSince INTEGER DEFAULT 0 NOT NULL,
BanReason TEXT DEFAULT '' NOT NULL, BanReason TEXT DEFAULT '' NOT NULL,
Email TEXT DEFAULT '' NOT NULL,
LastPasswordReset INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY(AccountID AUTOINCREMENT) PRIMARY KEY(AccountID AUTOINCREMENT)
); );

View File

@@ -170,11 +170,11 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata
if(!blocked) { if(!blocked) {
boostDrain = (int)(skill->values[0][power] * scalingFactor); boostDrain = (int)(skill->values[0][power] * scalingFactor);
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW; if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
plr->subtractCapped(CappedValueType::BATTERY_W, boostDrain); plr->batteryW -= boostDrain;
potionDrain = (int)(skill->values[1][power] * scalingFactor); potionDrain = (int)(skill->values[1][power] * scalingFactor);
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN; if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
plr->subtractCapped(CappedValueType::BATTERY_N, potionDrain); plr->batteryN -= potionDrain;
} }
sSkillResult_BatteryDrain result{}; sSkillResult_BatteryDrain result{};

View File

@@ -76,21 +76,29 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
response.iSetValue = plr->HP = setData->iSetValue; response.iSetValue = plr->HP = setData->iSetValue;
break; break;
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY : case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY :
plr->setCapped(CappedValueType::BATTERY_W, setData->iSetValue); plr->batteryW = setData->iSetValue;
// caps
if (plr->batteryW > 9999)
plr->batteryW = 9999;
response.iSetValue = plr->batteryW; response.iSetValue = plr->batteryW;
break; break;
case CN_GM_SET_VALUE_TYPE__NANO_BATTERY: case CN_GM_SET_VALUE_TYPE__NANO_BATTERY:
plr->setCapped(CappedValueType::BATTERY_N, setData->iSetValue); plr->batteryN = setData->iSetValue;
// caps
if (plr->batteryN > 9999)
plr->batteryN = 9999;
response.iSetValue = plr->batteryN; response.iSetValue = plr->batteryN;
break; break;
case CN_GM_SET_VALUE_TYPE__FUSION_MATTER: case CN_GM_SET_VALUE_TYPE__FUSION_MATTER:
plr->setCapped(CappedValueType::FUSIONMATTER, setData->iSetValue); Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
Missions::updateFusionMatter(sock);
response.iSetValue = plr->fusionmatter; response.iSetValue = plr->fusionmatter;
break; break;
case CN_GM_SET_VALUE_TYPE__CANDY: case CN_GM_SET_VALUE_TYPE__CANDY:
plr->setCapped(CappedValueType::TAROS, setData->iSetValue); response.iSetValue = plr->money = setData->iSetValue;
response.iSetValue = plr->money;
break; break;
case CN_GM_SET_VALUE_TYPE__SPEED: case CN_GM_SET_VALUE_TYPE__SPEED:
case CN_GM_SET_VALUE_TYPE__JUMP: case CN_GM_SET_VALUE_TYPE__JUMP:
@@ -140,60 +148,6 @@ static void setGMSpecialOnOff(CNSocket *sock, CNPacketData *data) {
// this is only used for muting players, so no need to update the client since that logic is server-side // this is only used for muting players, so no need to update the client since that logic is server-side
} }
static void setGMRewardRate(CNSocket *sock, CNPacketData *data) {
Player *plr = PlayerManager::getPlayer(sock);
// access check
if (plr->accountLevel > 30)
return;
auto req = (sP_CL2FE_GM_REQ_REWARD_RATE*)data->buf;
if (req->iGetSet != 0) {
double *rate = nullptr;
switch (req->iRewardType) {
case REWARD_TYPE_TAROS:
rate = plr->rateT;
break;
case REWARD_TYPE_FUSIONMATTER:
rate = plr->rateF;
break;
}
if (rate == nullptr) {
std::cout << "[WARN] Invalid reward type for setGMRewardRate(): " << req->iRewardType << std::endl;
return;
}
if (req->iSetRateValue < 0 || req->iSetRateValue > 1000) {
std::cout << "[WARN] Invalid rate value for setGMRewardRate(): " << req->iSetRateValue << " (must be between 0 and 1000)" << std::endl;
return;
}
switch (req->iRewardRateIndex) {
case RATE_SLOT_ALL:
for (int i = 0; i < 5; i++)
rate[i] = req->iSetRateValue / 100.0;
break;
case RATE_SLOT_COMBAT:
case RATE_SLOT_MISSION:
case RATE_SLOT_EGG:
case RATE_SLOT_RACING:
rate[req->iRewardRateIndex] = req->iSetRateValue / 100.0;
break;
}
}
INITSTRUCT(sP_FE2CL_GM_REP_REWARD_RATE_SUCC, resp);
for (int i = 0; i < 5; i++) {
// double to float
resp.afRewardRate_Taros[i] = plr->rateT[i];
resp.afRewardRate_FusionMatter[i] = plr->rateF[i];
}
sock->sendPacket(resp, P_FE2CL_GM_REP_REWARD_RATE_SUCC);
}
static void locatePlayer(CNSocket *sock, CNPacketData *data) { static void locatePlayer(CNSocket *sock, CNPacketData *data) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
@@ -417,7 +371,6 @@ void BuiltinCommands::init() {
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH, setGMSpecialSwitchPlayer); REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH, setGMSpecialSwitchPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF, setGMSpecialOnOff); REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF, setGMSpecialOnOff);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_REWARD_RATE, setGMRewardRate);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_LOCATION, locatePlayer); REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_LOCATION, locatePlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_KICK_PLAYER, kickPlayer); REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_KICK_PLAYER, kickPlayer);

View File

@@ -1,7 +1,6 @@
#include "Chat.hpp" #include "Chat.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "servers/Monitor.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
@@ -9,6 +8,8 @@
#include <assert.h> #include <assert.h>
std::vector<std::string> Chat::dump;
using namespace Chat; using namespace Chat;
static void chatHandler(CNSocket* sock, CNPacketData* data) { static void chatHandler(CNSocket* sock, CNPacketData* data) {
@@ -27,7 +28,7 @@ static void chatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
// send to client // send to client
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
@@ -50,7 +51,7 @@ static void menuChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
// send to client // send to client
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
@@ -102,14 +103,14 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg)); memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg));
std::map<CNSocket*, Player*>::iterator it; std::map<CNSocket*, Player*>::iterator it;
// This value is completely arbitrary, but these make the most sense when you consider the architecture of the game
switch (announcement->iAreaType) { switch (announcement->iAreaType) {
case 0: // area (all players in viewable chunks) case 0: // area (all players in viewable chunks)
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE); PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
break; break;
case 1: // channel case 1: // shard
case 2: // shard case 2: // world
break; // not applicable to OpenFusion
case 3: // global (all players) case 3: // global (all players)
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) { for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
CNSocket* allSock = it->first; CNSocket* allSock = it->first;
@@ -119,12 +120,9 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
break; break;
} }
std::string logLine = std::to_string(announcement->iAreaType) + " " std::string logLine = "[Bcast " + std::to_string(announcement->iAreaType) + "] " + PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
+ std::to_string(announcement->iAnnounceType) + " " std::cout << logLine << std::endl;
+ std::to_string(announcement->iDuringTime) + " " dump.push_back("**" + logLine + "**");
+ PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
std::cout << "Broadcast " << logLine << std::endl;
Monitor::bcasts.push_back(logLine);
} }
// Buddy freechatting // Buddy freechatting
@@ -157,7 +155,7 @@ static void buddyChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
@@ -187,7 +185,7 @@ static void buddyMenuChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
@@ -220,7 +218,7 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
resp.iEmoteCode = pacdat->iEmoteCode; resp.iEmoteCode = pacdat->iEmoteCode;
sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT); sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT);
@@ -243,7 +241,7 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
// send to client // send to client
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
@@ -266,7 +264,7 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
// send to client // send to client
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);

View File

@@ -8,6 +8,7 @@
#include <vector> #include <vector>
namespace Chat { namespace Chat {
extern std::vector<std::string> dump;
void init(); void init();
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD

View File

@@ -441,7 +441,11 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty), damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
plr->subtractCapped(CappedValueType::BATTERY_W, 6 + difficulty); if (plr->batteryW >= 6 + difficulty)
plr->batteryW -= 6 + difficulty;
else
plr->batteryW = 0;
damage.first = mob->takeDamage(sock, damage.first); damage.first = mob->takeDamage(sock, damage.first);
respdata[i].iID = mob->id; respdata[i].iID = mob->id;
@@ -686,7 +690,10 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
} }
plr->subtractCapped(CappedValueType::BATTERY_W, 6 + plr->level); if (plr->batteryW >= 6 + plr->level)
plr->batteryW -= 6 + plr->level;
else
plr->batteryW = 0;
damage.first = target->takeDamage(sock, damage.first); damage.first = target->takeDamage(sock, damage.first);
@@ -735,7 +742,7 @@ static int8_t addBullet(Player* plr, bool isGrenade) {
toAdd.weaponBoost = plr->batteryW > 0; toAdd.weaponBoost = plr->batteryW > 0;
if (toAdd.weaponBoost) { if (toAdd.weaponBoost) {
int boostCost = Rand::rand(11) + 20; int boostCost = Rand::rand(11) + 20;
plr->subtractCapped(CappedValueType::BATTERY_W, boostCost); plr->batteryW = boostCost > plr->batteryW ? 0 : plr->batteryW - boostCost;
} }
Bullets[plr->iID][findId] = toAdd; Bullets[plr->iID][findId] = toAdd;

View File

@@ -3,7 +3,6 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "servers/Monitor.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Items.hpp" #include "Items.hpp"
@@ -11,6 +10,8 @@
using namespace Email; using namespace Email;
std::vector<std::string> Email::dump;
// New email notification // New email notification
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) { static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp); INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp);
@@ -75,7 +76,7 @@ static void emailReceiveTaros(CNSocket* sock, CNPacketData* data) {
Database::EmailData email = Database::getEmail(plr->iID, pkt->iEmailIndex); Database::EmailData email = Database::getEmail(plr->iID, pkt->iEmailIndex);
// money transfer // money transfer
plr->addCapped(CappedValueType::TAROS, email.Taros); plr->money += email.Taros;
email.Taros = 0; email.Taros = 0;
// update Taros in email // update Taros in email
Database::updateEmailContent(&email); Database::updateEmailContent(&email);
@@ -274,7 +275,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
} }
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
plr->subtractCapped(CappedValueType::TAROS, cost); plr->money -= cost;
Database::EmailData email = { Database::EmailData email = {
(int)pkt->iTo_PCUID, // PlayerId (int)pkt->iTo_PCUID, // PlayerId
Database::getNextEmailIndex(pkt->iTo_PCUID), // MsgIndex Database::getNextEmailIndex(pkt->iTo_PCUID), // MsgIndex
@@ -291,7 +292,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
}; };
if (!Database::sendEmail(&email, attachments, plr)) { if (!Database::sendEmail(&email, attachments, plr)) {
plr->addCapped(CappedValueType::TAROS, cost); // give money back plr->money += cost; // give money back
// give items back // give items back
while (!attachments.empty()) { while (!attachments.empty()) {
sItemBase attachment = attachments.back(); sItemBase attachment = attachments.back();
@@ -323,7 +324,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody; std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody;
std::cout << logEmail << std::endl; std::cout << logEmail << std::endl;
Monitor::emails.push_back(logEmail); dump.push_back(logEmail);
// notification to recipient if online // notification to recipient if online
CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID); CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID);

View File

@@ -4,5 +4,7 @@
#include <string> #include <string>
namespace Email { namespace Email {
extern std::vector<std::string> dump;
void init(); void init();
} }

View File

@@ -117,121 +117,6 @@ sPCAppearanceData Player::getAppearanceData() {
return data; return data;
} }
bool Player::hasQuestBoost() const {
if (AEQUIP_COUNT < AEQUIP_COUNT_WITH_BOOSTERS)
return false;
const sItemBase& booster = Equip[10];
return booster.iID == 153 && booster.iOpt > 0;
}
bool Player::hasHunterBoost() const {
if (AEQUIP_COUNT < AEQUIP_COUNT_WITH_BOOSTERS)
return false;
const sItemBase& booster = Equip[11];
return booster.iID == 154 && booster.iOpt > 0;
}
bool Player::hasRacerBoost() const {
if (AEQUIP_COUNT < AEQUIP_COUNT_WITH_BOOSTERS)
return false;
const sItemBase& booster = Equip[9];
return booster.iID == 155 && booster.iOpt > 0;
}
bool Player::hasSuperBoost() const {
return Player::hasQuestBoost() && Player::hasHunterBoost() && Player::hasRacerBoost();
}
static int32_t getCap(CappedValueType type) {
switch (type) {
case CappedValueType::TAROS:
return PC_CANDY_MAX;
case CappedValueType::FUSIONMATTER:
return PC_FUSIONMATTER_MAX;
case CappedValueType::BATTERY_W:
return PC_BATTERY_MAX;
case CappedValueType::BATTERY_N:
return PC_BATTERY_MAX;
case CappedValueType::TAROS_IN_TRADE:
return PC_CANDY_MAX;
default:
return INT32_MAX;
}
}
static int32_t *getCappedValue(Player *player, CappedValueType type) {
switch (type) {
case CappedValueType::TAROS:
return &player->money;
case CappedValueType::FUSIONMATTER:
return &player->fusionmatter;
case CappedValueType::BATTERY_W:
return &player->batteryW;
case CappedValueType::BATTERY_N:
return &player->batteryN;
case CappedValueType::TAROS_IN_TRADE:
return &player->moneyInTrade;
default:
return nullptr;
}
}
void Player::addCapped(CappedValueType type, int32_t diff) {
if (diff <= 0)
return;
int32_t max = getCap(type);
int32_t *value = getCappedValue(this, type);
if (value == nullptr)
return;
if (diff > max)
diff = max;
if (*value + diff > max)
*value = max;
else
*value += diff;
}
void Player::subtractCapped(CappedValueType type, int32_t diff) {
if (diff <= 0)
return;
int32_t max = getCap(type);
int32_t *value = getCappedValue(this, type);
if (value == nullptr)
return;
if (diff > max)
diff = max;
if (*value - diff < 0)
*value = 0;
else
*value -= diff;
}
void Player::setCapped(CappedValueType type, int32_t value) {
int32_t max = getCap(type);
int32_t *valToSet = getCappedValue(this, type);
if (valToSet == nullptr)
return;
if (value < 0)
value = 0;
else if (value > max)
value = max;
*valToSet = value;
}
// TODO: this is less effiecient than it was, because of memset() // TODO: this is less effiecient than it was, because of memset()
void Player::enterIntoViewOf(CNSocket *sock) { void Player::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_PC_NEW, pkt); INITSTRUCT(sP_FE2CL_PC_NEW, pkt);

View File

@@ -33,19 +33,6 @@ std::map<int32_t, int32_t> Items::EventToDropMap;
std::map<int32_t, int32_t> Items::MobToDropMap; std::map<int32_t, int32_t> Items::MobToDropMap;
std::map<int32_t, ItemSet> Items::ItemSets; std::map<int32_t, ItemSet> Items::ItemSets;
// 1 week
#define NANOCOM_BOOSTER_DURATION 604800
// known general item ids
#define GENERALITEM_GUMBALL_ADAPTIUM 119
#define GENERALITEM_GUMBALL_BLASTONS 120
#define GENERALITEM_GUMBALL_COSMIX 121
#define GENERALITEM_FUSION_HUNTER_BOOSTER 153
#define GENERALITEM_IZ_RACER_BOOSTER 154
#define GENERALITEM_QUESTER_BOOSTER 155
#define GENERALITEM_SUPER_BOOSTER_DX 156
#ifdef ACADEMY #ifdef ACADEMY
std::map<int32_t, int32_t> Items::NanoCapsules; // crate id -> nano id std::map<int32_t, int32_t> Items::NanoCapsules; // crate id -> nano id
@@ -335,14 +322,14 @@ static void itemMoveHandler(CNSocket* sock, CNPacketData* data) {
// if equipping an item, validate that it's of the correct type for the slot // if equipping an item, validate that it's of the correct type for the slot
if ((SlotType)itemmove->eTo == SlotType::EQUIP) { if ((SlotType)itemmove->eTo == SlotType::EQUIP) {
if (fromItem->iType == 10 && itemmove->iToSlotNum != EQUIP_SLOT_VEHICLE) if (fromItem->iType == 10 && itemmove->iToSlotNum != 8)
return; // vehicle in wrong slot return; // vehicle in wrong slot
else if (fromItem->iType != 10 else if (fromItem->iType != 10
&& !(fromItem->iType == 0 && itemmove->iToSlotNum == 7) && !(fromItem->iType == 0 && itemmove->iToSlotNum == 7)
&& fromItem->iType != itemmove->iToSlotNum) && fromItem->iType != itemmove->iToSlotNum)
return; // something other than a vehicle or a weapon in a non-matching slot return; // something other than a vehicle or a weapon in a non-matching slot
else if (itemmove->iToSlotNum >= AEQUIP_COUNT_MINUS_BOOSTERS) else if (itemmove->iToSlotNum >= AEQUIP_COUNT) // TODO: reject slots >= 9?
return; // boosters can't be equipped via move packet return; // invalid slot
} }
// save items to response // save items to response
@@ -399,7 +386,7 @@ static void itemMoveHandler(CNSocket* sock, CNPacketData* data) {
} }
// unequip vehicle if equip slot 8 is 0 // unequip vehicle if equip slot 8 is 0
if (plr->Equip[EQUIP_SLOT_VEHICLE].iID == 0 && plr->iPCState & 8) { if (plr->Equip[8].iID == 0 && plr->iPCState & 8) {
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response); INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_OFF_SUCC); sock->sendPacket(response, P_FE2CL_PC_VEHICLE_OFF_SUCC);
@@ -443,19 +430,30 @@ static void itemDeleteHandler(CNSocket* sock, CNPacketData* data) {
sock->sendPacket(resp, P_FE2CL_REP_PC_ITEM_DELETE_SUCC); sock->sendPacket(resp, P_FE2CL_REP_PC_ITEM_DELETE_SUCC);
} }
static void useGumball(CNSocket* sock, CNPacketData* data) { static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf; auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf;
Player* player = PlayerManager::getPlayer(sock); Player* player = PlayerManager::getPlayer(sock);
if (request->iSlotNum < 0 || request->iSlotNum >= AINVEN_COUNT)
return; // sanity check
// gumball can only be used from inventory, so we ignore eIL // gumball can only be used from inventory, so we ignore eIL
sItemBase gumball = player->Inven[request->iSlotNum]; sItemBase gumball = player->Inven[request->iSlotNum];
sNano nano = player->Nanos[player->equippedNanos[request->iNanoSlot]]; sNano nano = player->Nanos[player->equippedNanos[request->iNanoSlot]];
// sanity check, check if gumball exists
if (!(gumball.iOpt > 0 && gumball.iType == 7 && gumball.iID>=119 && gumball.iID<=121)) {
std::cout << "[WARN] Gumball not found" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
return;
}
// sanity check, check if gumball type matches nano style // sanity check, check if gumball type matches nano style
int nanoStyle = Nanos::nanoStyle(nano.iID); int nanoStyle = Nanos::nanoStyle(nano.iID);
if (!((gumball.iID == GENERALITEM_GUMBALL_ADAPTIUM && nanoStyle == 0) || if (!((gumball.iID == 119 && nanoStyle == 0) ||
( gumball.iID == GENERALITEM_GUMBALL_BLASTONS && nanoStyle == 1) || ( gumball.iID == 120 && nanoStyle == 1) ||
( gumball.iID == GENERALITEM_GUMBALL_COSMIX && nanoStyle == 2))) { ( gumball.iID == 121 && nanoStyle == 2))) {
std::cout << "[WARN] Gumball type doesn't match nano type" << std::endl; std::cout << "[WARN] Gumball type doesn't match nano type" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response); INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL); sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
@@ -474,8 +472,11 @@ static void useGumball(CNSocket* sock, CNPacketData* data) {
return; return;
} }
uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; if (gumball.iOpt == 0)
memset(respbuf, 0, resplen); gumball = {};
uint8_t respbuf[CN_PACKET_BODY_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE);
sP_FE2CL_REP_PC_ITEM_USE_SUCC *resp = (sP_FE2CL_REP_PC_ITEM_USE_SUCC*)respbuf; sP_FE2CL_REP_PC_ITEM_USE_SUCC *resp = (sP_FE2CL_REP_PC_ITEM_USE_SUCC*)respbuf;
sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC)); sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
@@ -514,128 +515,6 @@ static void useGumball(CNSocket* sock, CNPacketData* data) {
player->Inven[resp->iSlotNum] = resp->RemainItem; player->Inven[resp->iSlotNum] = resp->RemainItem;
} }
static void useNanocomBooster(CNSocket* sock, CNPacketData* data) {
// Guard against using nanocom boosters in before and including 0104
// either path should be optimized by the compiler, effectively a no-op
if (AEQUIP_COUNT < AEQUIP_COUNT_WITH_BOOSTERS) {
std::cout << "[WARN] Nanocom Booster use not supported in this version" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, respFail);
sock->sendPacket(respFail, P_FE2CL_REP_PC_ITEM_USE_FAIL);
return;
}
auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf;
Player* player = PlayerManager::getPlayer(sock);
sItemBase item = player->Inven[request->iSlotNum];
// decide on the booster to activate
std::vector<int16_t> boosterIDs;
switch(item.iID) {
case GENERALITEM_FUSION_HUNTER_BOOSTER:
case GENERALITEM_IZ_RACER_BOOSTER:
case GENERALITEM_QUESTER_BOOSTER:
boosterIDs.push_back(item.iID);
break;
case GENERALITEM_SUPER_BOOSTER_DX:
boosterIDs.push_back(GENERALITEM_FUSION_HUNTER_BOOSTER);
boosterIDs.push_back(GENERALITEM_IZ_RACER_BOOSTER);
boosterIDs.push_back(GENERALITEM_QUESTER_BOOSTER);
break;
}
// consume item
item.iOpt -= 1;
if (item.iOpt == 0)
item = {};
// client wants to subtract server time in seconds from the time limit for display purposes
int32_t timeLimitDisplayed = (getTime() / 1000UL) + NANOCOM_BOOSTER_DURATION;
// in actuality we will use the timestamp of booster activation to the item time limit similar to vehicles
// and this is how it will be saved to the database
int32_t timeLimit = getTimestamp() + NANOCOM_BOOSTER_DURATION;
// give item(s) to inv slots
for (int16_t itemID : boosterIDs) {
sItemBase boosterItem = { 7, itemID, 1, timeLimitDisplayed };
// quester 155 -> 9, hunter 153 -> 10, racer 154 -> 11
int slot = 9 + ((itemID - GENERALITEM_FUSION_HUNTER_BOOSTER + 1) % 3);
// give item to the equip slot
INITSTRUCT(sP_FE2CL_REP_PC_GIVE_ITEM_SUCC, resp);
resp.eIL = (int)SlotType::EQUIP;
resp.iSlotNum = slot;
resp.Item = boosterItem;
sock->sendPacket(resp, P_FE2CL_REP_PC_GIVE_ITEM_SUCC);
// inform client of equip change (non visible so it's okay to just send to the player)
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, equipChange);
equipChange.iPC_ID = player->iID;
equipChange.iEquipSlotNum = slot;
equipChange.EquipSlotItem = boosterItem;
sock->sendPacket(equipChange, P_FE2CL_PC_EQUIP_CHANGE);
boosterItem.iTimeLimit = timeLimit;
// should replace existing booster in slot if it exists, i.e. you can refresh your boosters
player->Equip[slot] = boosterItem;
}
// send item use success packet
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_SUCC, respUse);
respUse.iPC_ID = player->iID;
respUse.eIL = (int)SlotType::INVENTORY;
respUse.iSlotNum = request->iSlotNum;
respUse.RemainItem = item;
sock->sendPacket(respUse, P_FE2CL_REP_PC_ITEM_USE_SUCC);
// update inventory serverside
player->Inven[request->iSlotNum] = item;
}
static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
auto request = (sP_CL2FE_REQ_ITEM_USE*)data->buf;
Player* player = PlayerManager::getPlayer(sock);
if (request->iSlotNum < 0 || request->iSlotNum >= AINVEN_COUNT)
return; // sanity check
sItemBase item = player->Inven[request->iSlotNum];
// sanity check, check the item exists and has correct iType
if (!(item.iOpt > 0 && item.iType == 7)) {
std::cout << "[WARN] General item not found" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
return;
}
/*
* TODO: In the XDT, there are subtypes for general-use items
* (m_pGeneralItemTable -> m_pItemData-> m_iItemType) that
* determine their behavior. It would be better to load these
* and use them in this switch, rather than hardcoding by IDs.
*/
switch(item.iID) {
case GENERALITEM_GUMBALL_ADAPTIUM:
case GENERALITEM_GUMBALL_BLASTONS:
case GENERALITEM_GUMBALL_COSMIX:
useGumball(sock, data);
break;
case GENERALITEM_FUSION_HUNTER_BOOSTER:
case GENERALITEM_IZ_RACER_BOOSTER:
case GENERALITEM_QUESTER_BOOSTER:
case GENERALITEM_SUPER_BOOSTER_DX:
useNanocomBooster(sock, data);
break;
default:
std::cout << "[INFO] General item "<< item.iID << " is unimplemented." << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
break;
}
}
static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) { static void itemBankOpenHandler(CNSocket* sock, CNPacketData* data) {
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
@@ -719,7 +598,7 @@ static void chestOpenHandler(CNSocket *sock, CNPacketData *data) {
// if we failed to open a crate, at least give the player a gumball (suggested by Jade) // if we failed to open a crate, at least give the player a gumball (suggested by Jade)
if (failing) { if (failing) {
item->sItem.iType = 7; item->sItem.iType = 7;
item->sItem.iID = GENERALITEM_GUMBALL_ADAPTIUM + Rand::rand(3); item->sItem.iID = 119 + Rand::rand(3);
item->sItem.iOpt = 1; item->sItem.iOpt = 1;
std::cout << "[WARN] Crate open failed, giving a Gumball..." << std::endl; std::cout << "[WARN] Crate open failed, giving a Gumball..." << std::endl;
@@ -753,89 +632,39 @@ Item* Items::getItemData(int32_t id, int32_t type) {
return nullptr; return nullptr;
} }
size_t Items::checkAndRemoveExpiredItems(CNSocket* sock, Player* player) { void Items::checkItemExpire(CNSocket* sock, Player* player) {
int32_t currentTime = getTimestamp(); if (player->toRemoveVehicle.eIL == 0 && player->toRemoveVehicle.iSlotNum == 0)
return;
// if there are expired items in bank just remove them silently /* prepare packet
if (settings::REMOVEEXPIREDITEMSFROMBANK) { * yes, this is a varadic packet, however analyzing client behavior and code
for (int i = 0; i < ABANK_COUNT; i++) { * it only checks takes the first item sent into account
if (player->Bank[i].iTimeLimit < currentTime && player->Bank[i].iTimeLimit != 0) { * yes, this is very stupid
memset(&player->Bank[i], 0, sizeof(sItemBase)); * therefore, we delete all but 1 expired vehicle while loading player
} * to delete the last one here so player gets a notification
} */
}
// collect items to remove and data for the packet const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL);
std::vector<sItemBase*> toRemove;
std::vector<sTimeLimitItemDeleteInfo2CL> itemData;
// equipped items
for (int i = 0; i < AEQUIP_COUNT; i++) {
if (player->Equip[i].iOpt > 0 && player->Equip[i].iTimeLimit < currentTime && player->Equip[i].iTimeLimit != 0) {
toRemove.push_back(&player->Equip[i]);
itemData.push_back({ (int)SlotType::EQUIP, i });
}
}
// inventory
for (int i = 0; i < AINVEN_COUNT; i++) {
if (player->Inven[i].iTimeLimit < currentTime && player->Inven[i].iTimeLimit != 0) {
toRemove.push_back(&player->Inven[i]);
itemData.push_back({ (int)SlotType::INVENTORY, i });
}
}
if (itemData.empty())
return 0;
// prepare packet containing all expired items to delete
// this is expected for academy
// pre-academy only checks the first item in the packet
const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL) * itemData.size();
// 8 bytes * 262 items = 2096 bytes, in total this shouldn't exceed 2500 bytes
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BODY_SIZE);
uint8_t respbuf[CN_PACKET_BODY_SIZE]; // we know it's only one trailing struct, so we can skip full validation
memset(respbuf, 0, CN_PACKET_BODY_SIZE); uint8_t respbuf[resplen]; // not a variable length array, don't worry
auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf; auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf;
sTimeLimitItemDeleteInfo2CL* itemData = (sTimeLimitItemDeleteInfo2CL*)(respbuf + sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM));
memset(respbuf, 0, resplen);
for (size_t i = 0; i < itemData.size(); i++) { packet->iItemListCount = 1;
auto itemToDeletePtr = (sTimeLimitItemDeleteInfo2CL*)( itemData->eIL = player->toRemoveVehicle.eIL;
respbuf + sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL) * i itemData->iSlotNum = player->toRemoveVehicle.iSlotNum;
);
itemToDeletePtr->eIL = itemData[i].eIL;
itemToDeletePtr->iSlotNum = itemData[i].iSlotNum;
packet->iItemListCount++;
}
sock->sendPacket((void*)&respbuf, P_FE2CL_PC_DELETE_TIME_LIMIT_ITEM, resplen); sock->sendPacket((void*)&respbuf, P_FE2CL_PC_DELETE_TIME_LIMIT_ITEM, resplen);
// delete items serverside and send unequip packets // delete serverside
for (size_t i = 0; i < itemData.size(); i++) { if (player->toRemoveVehicle.eIL == 0)
sItemBase* item = toRemove[i]; memset(&player->Equip[8], 0, sizeof(sItemBase));
memset(item, 0, sizeof(sItemBase)); else
memset(&player->Inven[player->toRemoveVehicle.iSlotNum], 0, sizeof(sItemBase));
// send item delete success packet player->toRemoveVehicle.eIL = 0;
// required for pre-academy builds player->toRemoveVehicle.iSlotNum = 0;
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_DELETE_SUCC, itemDelete);
itemDelete.eIL = itemData[i].eIL;
itemDelete.iSlotNum = itemData[i].iSlotNum;
sock->sendPacket(itemDelete, P_FE2CL_REP_PC_ITEM_DELETE_SUCC);
// also update item equips if needed
if (itemData[i].eIL == (int)SlotType::EQUIP) {
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, equipChange);
equipChange.iPC_ID = player->iID;
equipChange.iEquipSlotNum = itemData[i].iSlotNum;
sock->sendPacket(equipChange, P_FE2CL_PC_EQUIP_CHANGE);
}
}
// exit vehicle if player no longer has one equipped (function checks pcstyle)
if (player->Equip[EQUIP_SLOT_VEHICLE].iID == 0)
PlayerManager::exitPlayerVehicle(sock, nullptr);
return itemData.size();
} }
void Items::setItemStats(Player* plr) { void Items::setItemStats(Player* plr) {
@@ -882,69 +711,7 @@ static void getMobDrop(sItemBase* reward, const std::vector<int>& weights, const
reward->iID = crateIds[chosenIndex]; reward->iID = crateIds[chosenIndex];
} }
static int32_t calculateTaroReward(Player* plr, int baseAmount, int groupSize) { static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRoll& rolled) {
double bonus = plr->hasBuff(ECSB_REWARD_CASH) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
double groupEffect = settings::LESSTAROFMINGROUPDISABLED ? 1.0 : 1.0 / groupSize;
return baseAmount * plr->rateT[RATE_SLOT_COMBAT] * bonus * groupEffect;
}
static int32_t calculateFMReward(Player* plr, int baseAmount, int levelDiff, int groupSize) {
double scavenge = plr->hasBuff(ECSB_REWARD_BLOB) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
double boosterEffect = plr->hasHunterBoost() ? (plr->hasQuestBoost() && plr->hasRacerBoost() ? 1.75 : 1.5) : 1.0;
// if player is within 1 level of the mob, FM is untouched
double levelEffect = 1.0;
// otherwise, follow the table below
switch (std::clamp(levelDiff, -3, 6)) {
case 6:
// if player is 6 or more levels above mob, no FM is dropped
levelEffect = 0.0;
break;
case 5:
levelEffect = 0.25;
break;
case 4:
levelEffect = 0.5;
break;
case 3:
levelEffect = 0.75;
break;
case 2:
levelEffect = 0.899;
break;
case -2:
levelEffect = 1.1;
break;
case -3:
// if player is 3 or more levels below mob, FM is 1.2x
levelEffect = 1.2;
break;
}
// if no group, FM is untouched
double groupEffect = 1.0;
// otherwise, follow the table below
if (!settings::LESSTAROFMINGROUPDISABLED) {
switch (groupSize) {
case 2:
groupEffect = 0.875;
break;
case 3:
groupEffect = 0.75;
break;
case 4:
// this case is more lenient
groupEffect = 0.688;
break;
}
}
int32_t amount = baseAmount * plr->rateF[RATE_SLOT_COMBAT] * scavenge * levelEffect * groupEffect;
amount *= boosterEffect;
return amount;
}
static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRoll& rolled, int groupSize) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
@@ -996,20 +763,43 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
MiscDropType& miscDropType = Items::MiscDropTypes[drop.miscDropTypeId]; MiscDropType& miscDropType = Items::MiscDropTypes[drop.miscDropTypeId];
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) { if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
int32_t taros = calculateTaroReward(plr, miscDropType.taroAmount, groupSize); plr->money += miscDropType.taroAmount;
plr->addCapped(CappedValueType::TAROS, taros); // money nano boost
if (plr->hasBuff(ECSB_REWARD_CASH)) {
int boost = 0;
if (Nanos::getNanoBoost(plr)) // for gumballs
boost = 1;
plr->money += miscDropType.taroAmount * (5 + boost) / 25;
}
} }
if (rolled.fm % miscDropChance.fmDropChanceTotal < miscDropChance.fmDropChance) { if (rolled.fm % miscDropChance.fmDropChanceTotal < miscDropChance.fmDropChance) {
// formula for scaling FM with player/mob level difference
// TODO: adjust this better
int levelDifference = plr->level - mob->level; int levelDifference = plr->level - mob->level;
int32_t fm = calculateFMReward(plr, miscDropType.fmAmount, levelDifference, groupSize); int fm = miscDropType.fmAmount;
plr->addCapped(CappedValueType::FUSIONMATTER, fm); if (levelDifference > 0)
Missions::updateFusionMatter(sock); fm = levelDifference < 10 ? fm - (levelDifference * fm / 10) : 0;
// scavenger nano boost
if (plr->hasBuff(ECSB_REWARD_BLOB)) {
int boost = 0;
if (Nanos::getNanoBoost(plr)) // for gumballs
boost = 1;
fm += fm * (5 + boost) / 25;
}
Missions::updateFusionMatter(sock, fm);
} }
if (rolled.potions % miscDropChance.potionDropChanceTotal < miscDropChance.potionDropChance) if (rolled.potions % miscDropChance.potionDropChanceTotal < miscDropChance.potionDropChance)
plr->addCapped(CappedValueType::BATTERY_N, miscDropType.potionAmount); plr->batteryN += miscDropType.potionAmount;
if (rolled.boosts % miscDropChance.boostDropChanceTotal < miscDropChance.boostDropChance) if (rolled.boosts % miscDropChance.boostDropChanceTotal < miscDropChance.boostDropChance)
plr->addCapped(CappedValueType::BATTERY_W, miscDropType.boostAmount); plr->batteryW += miscDropType.boostAmount;
// caps
if (plr->batteryW > 9999)
plr->batteryW = 9999;
if (plr->batteryN > 9999)
plr->batteryN = 9999;
// simple rewards // simple rewards
reward->m_iCandy = plr->money; reward->m_iCandy = plr->money;
@@ -1040,7 +830,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
} }
} }
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled, int groupSize) { void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled) {
// sanity check // sanity check
if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) { if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl; std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
@@ -1049,7 +839,7 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const
// find mob drop id // find mob drop id
int mobDropId = Items::MobToDropMap[mob->type]; int mobDropId = Items::MobToDropMap[mob->type];
giveSingleDrop(sock, mob, mobDropId, rolled, groupSize); giveSingleDrop(sock, mob, mobDropId, rolled);
if (settings::EVENTMODE != 0) { if (settings::EVENTMODE != 0) {
// sanity check // sanity check
@@ -1060,13 +850,14 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const
// find mob drop id // find mob drop id
int eventMobDropId = Items::EventToDropMap[settings::EVENTMODE]; int eventMobDropId = Items::EventToDropMap[settings::EVENTMODE];
giveSingleDrop(sock, mob, eventMobDropId, eventRolled, groupSize); giveSingleDrop(sock, mob, eventMobDropId, eventRolled);
} }
} }
void Items::init() { void Items::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler);
// this one is for gumballs
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_USE, itemUseHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_USE, itemUseHandler);
// Bank // Bank
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BANK_OPEN, itemBankOpenHandler); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BANK_OPEN, itemBankOpenHandler);

View File

@@ -113,11 +113,11 @@ namespace Items {
void init(); void init();
// mob drops // mob drops
void giveMobDrop(CNSocket *sock, Mob *mob, const DropRoll& rolled, const DropRoll& eventRolled, int groupSize); void giveMobDrop(CNSocket *sock, Mob *mob, const DropRoll& rolled, const DropRoll& eventRolled);
int findFreeSlot(Player *plr); int findFreeSlot(Player *plr);
Item* getItemData(int32_t id, int32_t type); Item* getItemData(int32_t id, int32_t type);
size_t checkAndRemoveExpiredItems(CNSocket* sock, Player* player); void checkItemExpire(CNSocket* sock, Player* player);
void setItemStats(Player* plr); void setItemStats(Player* plr);
void updateEquips(CNSocket* sock, Player* plr); void updateEquips(CNSocket* sock, Player* plr);

View File

@@ -12,20 +12,6 @@ std::map<int32_t, Reward*> Missions::Rewards;
std::map<int32_t, TaskData*> Missions::Tasks; std::map<int32_t, TaskData*> Missions::Tasks;
nlohmann::json Missions::AvatarGrowth[37]; nlohmann::json Missions::AvatarGrowth[37];
static int32_t calculateTaroReward(Player* plr, int32_t baseAmount) {
double bonus = plr->hasBuff(ECSB_REWARD_CASH) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
return baseAmount * plr->rateT[RATE_SLOT_MISSION] * bonus;
}
static int32_t calculateFMReward(Player* plr, int32_t baseAmount) {
double scavenge = plr->hasBuff(ECSB_REWARD_BLOB) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
double missionBoost = plr->hasQuestBoost() ? (plr->hasHunterBoost() && plr->hasRacerBoost() ? 1.75 : 1.5) : 1.0;
int32_t reward = baseAmount * plr->rateF[RATE_SLOT_MISSION] * scavenge;
reward *= missionBoost;
return reward;
}
static void saveMission(Player* player, int missionId) { static void saveMission(Player* player, int missionId) {
// sanity check missionID so we don't get exceptions // sanity check missionID so we don't get exceptions
if (missionId < 0 || missionId > 1023) { if (missionId < 0 || missionId > 1023) {
@@ -176,12 +162,21 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, CN_PACKET_BODY_SIZE);
// update player // update player
int32_t money = calculateTaroReward(plr, reward->money); plr->money += reward->money;
plr->addCapped(CappedValueType::TAROS, money); if (plr->hasBuff(ECSB_REWARD_CASH)) { // nano boost for taros
int boost = 0;
if (Nanos::getNanoBoost(plr)) // for gumballs
boost = 1;
plr->money += reward->money * (5 + boost) / 25;
}
int32_t fusionMatter = calculateFMReward(plr, reward->fusionmatter); if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm
plr->addCapped(CappedValueType::FUSIONMATTER, fusionMatter); int boost = 0;
Missions::updateFusionMatter(sock); if (Nanos::getNanoBoost(plr)) // for gumballs
boost = 1;
updateFusionMatter(sock, reward->fusionmatter * (30 + boost) / 25);
} else
updateFusionMatter(sock, reward->fusionmatter);
// simple rewards // simple rewards
resp->m_iCandy = plr->money; resp->m_iCandy = plr->money;
@@ -516,13 +511,17 @@ void Missions::quitTask(CNSocket* sock, int32_t taskNum, bool manual) {
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC)); sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC));
} }
void Missions::updateFusionMatter(CNSocket* sock) { void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
plr->fusionmatter += fusion;
// there's a much lower FM cap in the Future // there's a much lower FM cap in the Future
int fmCap = AvatarGrowth[plr->level]["m_iFMLimit"]; int fmCap = AvatarGrowth[plr->level]["m_iFMLimit"];
if (plr->fusionmatter > fmCap) if (plr->fusionmatter > fmCap)
plr->fusionmatter = fmCap; plr->fusionmatter = fmCap;
else if (plr->fusionmatter < 0) // if somehow lowered too far
plr->fusionmatter = 0;
// don't run nano mission logic at level 36 // don't run nano mission logic at level 36
if (plr->level >= 36) if (plr->level >= 36)
@@ -552,7 +551,7 @@ void Missions::updateFusionMatter(CNSocket* sock) {
response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"]; response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"];
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
#else #else
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"]); plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
plr->level++; plr->level++;
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL_SUCC, response); INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL_SUCC, response);

View File

@@ -47,7 +47,7 @@ namespace Missions {
bool startTask(Player* plr, int TaskID); bool startTask(Player* plr, int TaskID);
// checks if player doesn't have n/n quest items // checks if player doesn't have n/n quest items
void updateFusionMatter(CNSocket* sock); void updateFusionMatter(CNSocket* sock, int fusion);
void mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls); void mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls);

View File

@@ -808,17 +808,18 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
Items::DropRoll rolled; Items::DropRoll rolled;
Items::DropRoll eventRolled; Items::DropRoll eventRolled;
std::map<int, int> qitemRolls; std::map<int, int> qitemRolls;
std::vector<EntityRef> playersInRange;
std::vector<Player*> playerRefs; std::vector<Player*> playerRefs;
if (plr->group == nullptr) { if (plr->group == nullptr) {
playerRefs.push_back(plr); playerRefs.push_back(plr);
Combat::genQItemRolls(playerRefs, qitemRolls); Combat::genQItemRolls(playerRefs, qitemRolls);
Items::giveMobDrop(src.sock, self, rolled, eventRolled, 1); Items::giveMobDrop(src.sock, self, rolled, eventRolled);
Missions::mobKilled(src.sock, self->type, qitemRolls); Missions::mobKilled(src.sock, self->type, qitemRolls);
} }
else { else {
auto players = plr->group->filter(EntityKind::PLAYER); auto players = plr->group->filter(EntityKind::PLAYER);
for (EntityRef pRef : players) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock));
Combat::genQItemRolls(playerRefs, qitemRolls);
for (int i = 0; i < players.size(); i++) { for (int i = 0; i < players.size(); i++) {
CNSocket* sockTo = players[i].sock; CNSocket* sockTo = players[i].sock;
Player* otherPlr = PlayerManager::getPlayer(sockTo); Player* otherPlr = PlayerManager::getPlayer(sockTo);
@@ -828,14 +829,7 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
if (dist > 5000) if (dist > 5000)
continue; continue;
playersInRange.push_back(players[i]); Items::giveMobDrop(sockTo, self, rolled, eventRolled);
}
for (EntityRef pRef : playersInRange) playerRefs.push_back(PlayerManager::getPlayer(pRef.sock));
Combat::genQItemRolls(playerRefs, qitemRolls);
for (int i = 0; i < playersInRange.size(); i++) {
CNSocket* sockTo = playersInRange[i].sock;
Items::giveMobDrop(sockTo, self, rolled, eventRolled, playersInRange.size());
Missions::mobKilled(sockTo, self->type, qitemRolls); Missions::mobKilled(sockTo, self->type, qitemRolls);
} }
} }

View File

@@ -33,10 +33,8 @@ void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm)
*/ */
plr->level = level; plr->level = level;
if (spendfm) { if (spendfm)
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]); Missions::updateFusionMatter(sock, -(int)Missions::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
Missions::updateFusionMatter(sock);
}
#endif #endif
// Send to client // Send to client
@@ -145,7 +143,7 @@ static void setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill) {
return; return;
#endif #endif
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"]); plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"];
int reqItemCount = NanoTunings[skill->iTuneID].reqItemCount; int reqItemCount = NanoTunings[skill->iTuneID].reqItemCount;
int reqItemID = NanoTunings[skill->iTuneID].reqItems; int reqItemID = NanoTunings[skill->iTuneID].reqItems;
@@ -357,7 +355,7 @@ static void nanoPotionHandler(CNSocket* sock, CNPacketData* data) {
sock->sendPacket(response, P_FE2CL_REP_CHARGE_NANO_STAMINA); sock->sendPacket(response, P_FE2CL_REP_CHARGE_NANO_STAMINA);
// now update serverside // now update serverside
player->subtractCapped(CappedValueType::BATTERY_N, difference); player->batteryN -= difference;
player->Nanos[nano.iID].iStamina += difference; player->Nanos[nano.iID].iStamina += difference;
} }

View File

@@ -15,14 +15,6 @@ struct BuffStack;
#define PC_MAXHEALTH(level) (925 + 75 * (level)) #define PC_MAXHEALTH(level) (925 + 75 * (level))
enum class CappedValueType {
TAROS,
FUSIONMATTER,
BATTERY_W,
BATTERY_N,
TAROS_IN_TRADE,
};
struct Player : public Entity, public ICombatant { struct Player : public Entity, public ICombatant {
int accountId = 0; int accountId = 0;
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums) int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
@@ -73,6 +65,8 @@ struct Player : public Entity, public ICombatant {
sItemBase QInven[AQINVEN_COUNT] = {}; sItemBase QInven[AQINVEN_COUNT] = {};
int32_t CurrentMissionID = 0; int32_t CurrentMissionID = 0;
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
Group* group = nullptr; Group* group = nullptr;
bool notify = false; bool notify = false;
@@ -90,14 +84,7 @@ struct Player : public Entity, public ICombatant {
time_t lastShot = 0; time_t lastShot = 0;
std::vector<sItemBase> buyback = {}; std::vector<sItemBase> buyback = {};
double rateF[5] = { 1.0, 1.0, 1.0, 1.0, 1.0 }; Player() { kind = EntityKind::PLAYER; }
double rateT[5] = { 1.0, 1.0, 1.0, 1.0, 1.0 };
Player() {
kind = EntityKind::PLAYER;
std::fill_n(rateF, 5, (double)settings::FUSIONMATTERRATE / 100.0);
std::fill_n(rateT, 5, (double)settings::TARORATE / 100.0);
}
virtual void enterIntoViewOf(CNSocket *sock) override; virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
@@ -124,11 +111,4 @@ struct Player : public Entity, public ICombatant {
sNano* getActiveNano(); sNano* getActiveNano();
sPCAppearanceData getAppearanceData(); sPCAppearanceData getAppearanceData();
bool hasQuestBoost() const;
bool hasHunterBoost() const;
bool hasRacerBoost() const;
bool hasSuperBoost() const;
void addCapped(CappedValueType type, int32_t diff);
void subtractCapped(CappedValueType type, int32_t diff);
void setCapped(CappedValueType type, int32_t value);
}; };

View File

@@ -243,19 +243,9 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
// 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;
// equipment (except nanocom boosters)
for (int i = 0; i < AEQUIP_COUNT_MINUS_BOOSTERS; i++)
response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
// equipment (nanocom boosters, loop only runs if boosters are available)
int32_t serverTime = getTime() / 1000UL;
int32_t timestamp = getTimestamp();
for (int i = AEQUIP_COUNT_MINUS_BOOSTERS; i < AEQUIP_COUNT; i++) {
response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
// client subtracts server time, then adds local timestamp to the item to print expiration time
response.PCLoadData2CL.aEquip[i].iTimeLimit = std::max(0, plr->Equip[i].iTimeLimit - timestamp + serverTime);
}
// inventory // inventory
for (int i = 0; i < AEQUIP_COUNT; 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
@@ -394,7 +384,7 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) {
Chat::sendServerMessage(sock, settings::MOTDSTRING); // MOTD Chat::sendServerMessage(sock, settings::MOTDSTRING); // MOTD
Missions::failInstancedMissions(sock); // auto-fail missions Missions::failInstancedMissions(sock); // auto-fail missions
Buddies::sendBuddyList(sock); // buddy list Buddies::sendBuddyList(sock); // buddy list
Items::checkAndRemoveExpiredItems(sock, plr); // vehicle and booster expiration Items::checkItemExpire(sock, plr); // vehicle expiration
plr->initialLoadDone = true; plr->initialLoadDone = true;
} }
@@ -406,14 +396,6 @@ static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) {
static void exitGame(CNSocket* sock, CNPacketData* data) { static void exitGame(CNSocket* sock, CNPacketData* data) {
auto exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf; auto exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf;
// Refresh any auth cookie, in case "change character" was used
Player* plr = getPlayer(sock);
if (plr != nullptr) {
// 5 seconds should be enough to log in again
Database::refreshCookie(plr->accountId, 5);
}
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response); INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
response.iID = exitData->iID; response.iID = exitData->iID;
@@ -505,6 +487,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
resp2.PCRegenDataForOtherPC.iAngle = plr->angle; resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
if (plr->group != nullptr) { if (plr->group != nullptr) {
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition(); resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState; resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState; resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
@@ -526,7 +509,9 @@ static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
if (plr->instanceID != 0) if (plr->instanceID != 0)
return; return;
if (plr->Equip[EQUIP_SLOT_VEHICLE].iID > 0) { bool expired = plr->Equip[8].iTimeLimit < getTimestamp() && plr->Equip[8].iTimeLimit != 0;
if (plr->Equip[8].iID > 0 && !expired) {
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_SUCC, response); INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_SUCC, response);
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_ON_SUCC); sock->sendPacket(response, P_FE2CL_PC_VEHICLE_ON_SUCC);
@@ -540,6 +525,30 @@ static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
} else { } else {
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_FAIL, response); INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_FAIL, response);
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_ON_FAIL); sock->sendPacket(response, P_FE2CL_PC_VEHICLE_ON_FAIL);
// check if vehicle didn't expire
if (expired) {
plr->toRemoveVehicle.eIL = 0;
plr->toRemoveVehicle.iSlotNum = 8;
Items::checkItemExpire(sock, plr);
}
}
}
static void exitPlayerVehicle(CNSocket* sock, CNPacketData* data) {
Player* plr = getPlayer(sock);
if (plr->iPCState & 8) {
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_OFF_SUCC);
// send to other players
plr->iPCState &= ~8;
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
response2.iPC_ID = plr->iID;
response2.iState = plr->iPCState;
sendToViewable(sock, response2, P_FE2CL_PC_STATE_CHANGE);
} }
} }
@@ -568,7 +577,7 @@ static void changePlayerGuide(CNSocket *sock, CNPacketData *data) {
} }
// start Blossom nano mission if applicable // start Blossom nano mission if applicable
Missions::updateFusionMatter(sock); Missions::updateFusionMatter(sock, 0);
} }
// save it on player // save it on player
plr->mentor = pkt->iMentor; plr->mentor = pkt->iMentor;
@@ -590,23 +599,6 @@ static void setFirstUseFlag(CNSocket* sock, CNPacketData* data) {
} }
#pragma region Helper methods #pragma region Helper methods
void PlayerManager::exitPlayerVehicle(CNSocket* sock, CNPacketData* data) {
Player* plr = getPlayer(sock);
if (plr->iPCState & 8) {
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_OFF_SUCC);
// send to other players
plr->iPCState &= ~8;
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
response2.iPC_ID = plr->iID;
response2.iState = plr->iPCState;
sendToViewable(sock, response2, P_FE2CL_PC_STATE_CHANGE);
}
}
Player *PlayerManager::getPlayer(CNSocket* key) { Player *PlayerManager::getPlayer(CNSocket* key) {
if (players.find(key) != players.end()) if (players.find(key) != players.end())
return players[key]; return players[key];
@@ -620,10 +612,6 @@ std::string PlayerManager::getPlayerName(Player *plr, bool id) {
if (plr == nullptr) if (plr == nullptr)
return "NOT IN GAME"; return "NOT IN GAME";
if (plr->PCStyle.iNameCheck != 1) {
return "Player " + std::to_string(plr->iID);
}
std::string ret = ""; std::string ret = "";
if (id && plr->accountLevel <= 30) if (id && plr->accountLevel <= 30)
ret += "(GM) "; ret += "(GM) ";

View File

@@ -14,8 +14,6 @@ namespace PlayerManager {
extern std::map<CNSocket*, Player*> players; extern std::map<CNSocket*, Player*> players;
void init(); void init();
void exitPlayerVehicle(CNSocket* sock, CNPacketData* data);
void removePlayer(CNSocket* key); void removePlayer(CNSocket* key);
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle); void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle);

View File

@@ -7,7 +7,6 @@
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Items.hpp" #include "Items.hpp"
#include "Nanos.hpp"
using namespace Racing; using namespace Racing;
@@ -15,15 +14,6 @@ std::map<int32_t, EPInfo> Racing::EPData;
std::map<CNSocket*, EPRace> Racing::EPRaces; std::map<CNSocket*, EPRace> Racing::EPRaces;
std::map<int32_t, std::pair<std::vector<int>, std::vector<int>>> Racing::EPRewards; std::map<int32_t, std::pair<std::vector<int>, std::vector<int>>> Racing::EPRewards;
static int32_t calculateFMReward(Player* plr, int32_t baseAmount) {
double scavenge = plr->hasBuff(ECSB_REWARD_BLOB) ? (Nanos::getNanoBoost(plr) ? 1.23 : 1.2) : 1.0;
double raceBoost = plr->hasRacerBoost() ? (plr->hasHunterBoost() && plr->hasQuestBoost() ? 1.75 : 1.5) : 1.0;
int32_t reward = baseAmount * plr->rateF[RATE_SLOT_RACING] * scavenge;
reward *= raceBoost;
return reward;
}
static void racingStart(CNSocket* sock, CNPacketData* data) { static void racingStart(CNSocket* sock, CNPacketData* data) {
auto req = (sP_CL2FE_REQ_EP_RACE_START*)data->buf; auto req = (sP_CL2FE_REQ_EP_RACE_START*)data->buf;
@@ -165,9 +155,7 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
resp.iEPRaceMode = EPRaces[sock].mode; resp.iEPRaceMode = EPRaces[sock].mode;
resp.iEPRewardFM = fm; resp.iEPRewardFM = fm;
int32_t fmReward = calculateFMReward(plr, resp.iEPRewardFM); Missions::updateFusionMatter(sock, resp.iEPRewardFM);
plr->addCapped(CappedValueType::FUSIONMATTER, fmReward);
Missions::updateFusionMatter(sock);
resp.iFusionMatter = plr->fusionmatter; resp.iFusionMatter = plr->fusionmatter;
resp.iFatigue = 50; resp.iFatigue = 50;

View File

@@ -1,7 +1,5 @@
#include "TableData.hpp" #include "TableData.hpp"
#include "servers/CNLoginServer.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Items.hpp" #include "Items.hpp"
@@ -81,25 +79,6 @@ static void loadXDT(json& xdtData) {
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"]; NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
try { try {
// load name wheel names
json firstNameData = xdtData["m_pNameTable"]["m_pFirstName"];
for (json::iterator _name = firstNameData.begin(); _name != firstNameData.end(); _name++) {
auto name = _name.value();
LoginServer::WheelFirstNames.push_back(name["m_pstrNameString"]);
}
json middleNameData = xdtData["m_pNameTable"]["m_pMiddleName"];
for (json::iterator _name = middleNameData.begin(); _name != middleNameData.end(); _name++) {
auto name = _name.value();
LoginServer::WheelMiddleNames.push_back(name["m_pstrNameString"]);
}
json lastNameData = xdtData["m_pNameTable"]["m_pLastName"];
for (json::iterator _name = lastNameData.begin(); _name != lastNameData.end(); _name++) {
auto name = _name.value();
LoginServer::WheelLastNames.push_back(name["m_pstrNameString"]);
}
// load warps // load warps
json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"]; json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];

View File

@@ -12,7 +12,7 @@
// these are added to the NPC's static key to avoid collisions // these are added to the NPC's static key to avoid collisions
const int NPC_ID_OFFSET = 1; const int NPC_ID_OFFSET = 1;
const int MOB_ID_OFFSET = 10000; const int MOB_ID_OFFSET = 10000;
const int MOB_GROUP_ID_OFFSET = 30000; const int MOB_GROUP_ID_OFFSET = 20000;
// typedef for JSON object because I don't want to type nlohmann::json every time // typedef for JSON object because I don't want to type nlohmann::json every time
typedef nlohmann::json json; typedef nlohmann::json json;

View File

@@ -273,16 +273,14 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
resp2.iID_Request = pacdat->iID_Request; resp2.iID_Request = pacdat->iID_Request;
resp2.iID_From = pacdat->iID_From; resp2.iID_From = pacdat->iID_From;
resp2.iID_To = pacdat->iID_To; resp2.iID_To = pacdat->iID_To;
plr->subtractCapped(CappedValueType::TAROS, plr->moneyInTrade); plr->money = plr->money + plr2->moneyInTrade - plr->moneyInTrade;
plr->addCapped(CappedValueType::TAROS, plr2->moneyInTrade);
resp2.iCandy = plr->money; resp2.iCandy = plr->money;
memcpy(resp2.Item, plr2->Trade, sizeof(plr2->Trade)); memcpy(resp2.Item, plr2->Trade, sizeof(plr2->Trade));
memcpy(resp2.ItemStay, plr->Trade, sizeof(plr->Trade)); memcpy(resp2.ItemStay, plr->Trade, sizeof(plr->Trade));
sock->sendPacket((void*)&resp2, P_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC)); sock->sendPacket((void*)&resp2, P_FE2CL_REP_PC_TRADE_CONFIRM_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CONFIRM_SUCC));
plr2->subtractCapped(CappedValueType::TAROS, plr2->moneyInTrade); plr2->money = plr2->money + plr->moneyInTrade - plr2->moneyInTrade;
plr2->addCapped(CappedValueType::TAROS, plr->moneyInTrade);
resp2.iCandy = plr2->money; resp2.iCandy = plr2->money;
memcpy(resp2.Item, plr->Trade, sizeof(plr->Trade)); memcpy(resp2.Item, plr->Trade, sizeof(plr->Trade));
memcpy(resp2.ItemStay, plr2->Trade, sizeof(plr2->Trade)); memcpy(resp2.ItemStay, plr2->Trade, sizeof(plr2->Trade));
@@ -453,7 +451,7 @@ static void tradeRegisterCash(CNSocket* sock, CNPacketData* data) {
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC)); sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC));
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC)); otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC));
plr->setCapped(CappedValueType::TAROS_IN_TRADE, pacdat->iCandy); plr->moneyInTrade = pacdat->iCandy;
plr->isTradeConfirm = false; plr->isTradeConfirm = false;
} }

View File

@@ -117,7 +117,7 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
} }
TransportRoute route = Routes[req->iTransporationID]; TransportRoute route = Routes[req->iTransporationID];
plr->subtractCapped(CappedValueType::TAROS, route.cost); plr->money -= route.cost;
TransportLocation* target = nullptr; TransportLocation* target = nullptr;
switch (route.type) { switch (route.type) {
@@ -143,7 +143,7 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
} }
// refund and send alert packet // refund and send alert packet
plr->addCapped(CappedValueType::TAROS, route.cost); plr->money += route.cost;
INITSTRUCT(sP_FE2CL_ANNOUNCE_MSG, alert); INITSTRUCT(sP_FE2CL_ANNOUNCE_MSG, alert);
alert.iAnnounceType = 0; // don't think this lets us make a confirm dialog alert.iAnnounceType = 0; // don't think this lets us make a confirm dialog
alert.iDuringTime = 3; alert.iDuringTime = 3;

View File

@@ -72,7 +72,7 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp);
plr->subtractCapped(CappedValueType::TAROS, itemCost); plr->money = plr->money - itemCost;
plr->Inven[slot] = req->Item; plr->Inven[slot] = req->Item;
resp.iCandy = plr->money; resp.iCandy = plr->money;
@@ -117,7 +117,7 @@ static void vendorSell(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_SELL_SUCC, resp);
// increment taros // increment taros
plr->addCapped(CappedValueType::TAROS, itemData->sellPrice * req->iItemCnt); plr->money += itemData->sellPrice * req->iItemCnt;
// modify item // modify item
if (item->iOpt - req->iItemCnt > 0) { // selling part of a stack if (item->iOpt - req->iItemCnt > 0) { // selling part of a stack
@@ -209,7 +209,7 @@ static void vendorBuyback(CNSocket* sock, CNPacketData* data) {
std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl; std::cout << "[WARN] Client and server disagree on bought item slot (" << req->iInvenSlotNum << " vs " << slot << ")" << std::endl;
} }
plr->subtractCapped(CappedValueType::TAROS, itemCost); plr->money = plr->money - itemCost;
plr->Inven[slot] = item; plr->Inven[slot] = item;
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_RESTORE_BUY_SUCC, resp);
@@ -273,7 +273,7 @@ static void vendorBuyBattery(CNSocket* sock, CNPacketData* data) {
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
int cost = req->Item.iOpt * 100; int cost = req->Item.iOpt * 100;
if ((req->Item.iID == 3 ? (plr->batteryW >= PC_BATTERY_MAX) : (plr->batteryN >= PC_BATTERY_MAX)) || plr->money < cost || req->Item.iOpt < 0) { // sanity check if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost || req->Item.iOpt < 0) { // sanity check
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp); INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp);
failResp.iErrorCode = 0; failResp.iErrorCode = 0;
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL); sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL);
@@ -281,11 +281,17 @@ static void vendorBuyBattery(CNSocket* sock, CNPacketData* data) {
} }
cost = plr->batteryW + plr->batteryN; cost = plr->batteryW + plr->batteryN;
plr->addCapped(CappedValueType::BATTERY_W, req->Item.iID == 3 ? req->Item.iOpt * 100 : 0); plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 100 : 0;
plr->addCapped(CappedValueType::BATTERY_N, req->Item.iID == 4 ? req->Item.iOpt * 100 : 0); plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 100 : 0;
// caps
if (plr->batteryW > 9999)
plr->batteryW = 9999;
if (plr->batteryN > 9999)
plr->batteryN = 9999;
cost = plr->batteryW + plr->batteryN - cost; cost = plr->batteryW + plr->batteryN - cost;
plr->subtractCapped(CappedValueType::TAROS, cost); plr->money -= cost;
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp);
@@ -358,7 +364,7 @@ static void vendorCombineItems(CNSocket* sock, CNPacketData* data) {
float rolled = Rand::randFloat(100.0f); // success chance out of 100 float rolled = Rand::randFloat(100.0f); // success chance out of 100
//std::cout << rolled << " vs " << successChance << std::endl; //std::cout << rolled << " vs " << successChance << std::endl;
plr->subtractCapped(CappedValueType::TAROS, cost); plr->money -= cost;
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp);

View File

@@ -365,8 +365,14 @@ void CNServer::init() {
} }
// attach socket to the port // attach socket to the port
if (!setSocketOption(sock, SOL_SOCKET, SO_REUSEADDR, 1)) { int opt = 1;
#ifdef _WIN32
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt)) != 0) {
#else
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
#endif
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl; std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
printSocketError("setsockopt");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
address.sin_family = AF_INET; address.sin_family = AF_INET;
@@ -408,18 +414,6 @@ void CNServer::init() {
CNServer::CNServer() {}; CNServer::CNServer() {};
CNServer::CNServer(uint16_t p): port(p) {} CNServer::CNServer(uint16_t p): port(p) {}
bool CNServer::setSocketOption(SOCKET s, int level, int option, int value) {
#ifdef _WIN32
if (setsockopt(s, level, option, (const char*)&value, sizeof(value)) != 0) {
#else
if (setsockopt(s, level, option, &value, sizeof(value)) != 0) {
#endif
printSocketError("setsockopt");
return false;
}
return true;
}
void CNServer::addPollFD(SOCKET s) { void CNServer::addPollFD(SOCKET s) {
fds.push_back({s, POLLIN}); fds.push_back({s, POLLIN});
} }
@@ -468,9 +462,6 @@ void CNServer::start() {
continue; continue;
} }
if (!setSocketOption(newConnectionSocket, IPPROTO_TCP, TCP_NODELAY, 1))
std::cout << "[WARN] OpenFusion: failed to set TCP_NODELAY on new connection" << std::endl;
if (!setSockNonblocking(sock, newConnectionSocket)) if (!setSockNonblocking(sock, newConnectionSocket))
continue; continue;

View File

@@ -28,7 +28,6 @@
// posix platform // posix platform
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <poll.h> #include <poll.h>
#include <unistd.h> #include <unistd.h>
@@ -240,7 +239,6 @@ protected:
bool active = true; bool active = true;
bool setSocketOption(SOCKET s, int level, int option, int value);
void addPollFD(SOCKET s); void addPollFD(SOCKET s);
void removePollFD(int i); void removePollFD(int i);

View File

@@ -67,7 +67,4 @@ void terminate(int);
#error Invalid PROTOCOL_VERSION #error Invalid PROTOCOL_VERSION
#endif #endif
#define AEQUIP_COUNT_MINUS_BOOSTERS 9
#define AEQUIP_COUNT_WITH_BOOSTERS 12
sSYSTEMTIME timeStampToStruct(uint64_t time); sSYSTEMTIME timeStampToStruct(uint64_t time);

View File

@@ -225,15 +225,6 @@ enum {
SIZEOF_NANO_TUNE_NEED_ITEM_SLOT = 10, SIZEOF_NANO_TUNE_NEED_ITEM_SLOT = 10,
VALUE_ATTACK_MISS = 1, VALUE_ATTACK_MISS = 1,
REWARD_TYPE_TAROS = 0,
REWARD_TYPE_FUSIONMATTER = 1,
RATE_SLOT_ALL = 0,
RATE_SLOT_COMBAT = 1,
RATE_SLOT_MISSION = 2,
RATE_SLOT_EGG = 3,
RATE_SLOT_RACING = 4,
MSG_ONLINE = 1, MSG_ONLINE = 1,
MSG_BUSY = 2, MSG_BUSY = 2,
MSG_OFFLINE = 0, MSG_OFFLINE = 0,

View File

@@ -5,7 +5,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#define DATABASE_VERSION 6 #define DATABASE_VERSION 5
namespace Database { namespace Database {
@@ -56,7 +56,6 @@ namespace Database {
// return true if cookie is valid for the account. // return true if cookie is valid for the account.
// invalidates the stored cookie afterwards // invalidates the stored cookie afterwards
bool checkCookie(int accountId, const char *cookie); bool checkCookie(int accountId, const char *cookie);
void refreshCookie(int accountId, int durationSec);
// interface for the /ban command // interface for the /ban command
bool banPlayer(int playerId, std::string& reason); bool banPlayer(int playerId, std::string& reason);
@@ -69,7 +68,7 @@ namespace Database {
bool isNameFree(std::string firstName, std::string lastName); bool isNameFree(std::string firstName, std::string lastName);
bool isSlotFree(int accountId, int slotNum); bool isSlotFree(int accountId, int slotNum);
/// returns ID, 0 if something failed /// returns ID, 0 if something failed
int createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck); int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID);
/// returns true if query succeeded /// returns true if query succeeded
bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId); bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId);
/// returns true if query succeeded /// returns true if query succeeded
@@ -85,7 +84,7 @@ namespace Database {
}; };
void evaluateCustomName(int characterID, CustomName decision); void evaluateCustomName(int characterID, CustomName decision);
/// returns true if query succeeded /// returns true if query succeeded
bool changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck); bool changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId);
// getting players // getting players
void getPlayer(Player* plr, int id); void getPlayer(Player* plr, int id);

View File

@@ -196,12 +196,14 @@ void Database::updateEmailContent(EmailData* data) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
static void _deleteEmailAttachments(int playerID, int index, int slot) { void Database::deleteEmailAttachments(int playerID, int index, int slot) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
std::string sql(R"( std::string sql(R"(
DELETE FROM EmailItems DELETE FROM EmailItems
WHERE PlayerID = ? AND MsgIndex = ? WHERE PlayerID = ? AND MsgIndex = ?;
)"); )");
if (slot != -1) if (slot != -1)
@@ -219,11 +221,6 @@ static void _deleteEmailAttachments(int playerID, int index, int slot) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
void Database::deleteEmailAttachments(int playerID, int index, int slot) {
std::lock_guard<std::mutex> lock(dbCrit);
_deleteEmailAttachments(playerID, index, slot);
}
void Database::deleteEmails(int playerID, int64_t* indices) { void Database::deleteEmails(int playerID, int64_t* indices) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
@@ -237,15 +234,12 @@ void Database::deleteEmails(int playerID, int64_t* indices) {
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
int64_t msgIndex = indices[i];
sqlite3_bind_int(stmt, 1, playerID); sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int64(stmt, 2, msgIndex); sqlite3_bind_int64(stmt, 2, indices[i]);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to delete an email: " << sqlite3_errmsg(db) << std::endl; std::cout << "[WARN] Database: Failed to delete an email: " << sqlite3_errmsg(db) << std::endl;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
// delete all attachments
_deleteEmailAttachments(playerID, msgIndex, -1);
} }
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@@ -329,23 +323,12 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Pl
sqlite3_bind_int(stmt, 7, item.iTimeLimit); sqlite3_bind_int(stmt, 7, item.iTimeLimit);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
// very likely the UNIQUE constraint failing due to
// orphaned attachments from an old email.
// try deleting them first
_deleteEmailAttachments(data->PlayerId, data->MsgIndex, -1);
// try again
sqlite3_reset(stmt);
if (sqlite3_step(stmt) != SQLITE_DONE) {
// different error, give up
std::cout << "[WARN] Database: Failed to send email: " << sqlite3_errmsg(db) << std::endl; std::cout << "[WARN] Database: Failed to send email: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return false; return false;
} }
}
sqlite3_reset(stmt); sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
} }
sqlite3_finalize(stmt); sqlite3_finalize(stmt);

View File

@@ -2,8 +2,6 @@
#include "db/internal.hpp" #include "db/internal.hpp"
#include "servers/CNLoginServer.hpp"
#include "bcrypt/BCrypt.hpp" #include "bcrypt/BCrypt.hpp"
void Database::findAccount(Account* account, std::string login) { void Database::findAccount(Account* account, std::string login) {
@@ -153,27 +151,6 @@ bool Database::checkCookie(int accountId, const char *tryCookie) {
return match; return match;
} }
void Database::refreshCookie(int accountId, int durationSec) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
UPDATE Auth
SET Expires = ?
WHERE AccountID = ?;
)";
int expires = getTimestamp() + durationSec;
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, expires);
sqlite3_bind_int(stmt, 2, accountId);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
std::cout << "[WARN] Database fail on refreshCookie(): " << sqlite3_errmsg(db) << std::endl;
}
void Database::updateSelected(int accountId, int slot) { void Database::updateSelected(int accountId, int slot) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
@@ -293,7 +270,7 @@ bool Database::isSlotFree(int accountId, int slotNum) {
return result; return result;
} }
int Database::createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck) { int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
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);
@@ -306,17 +283,22 @@ int Database::createCharacter(int slot, int accountId, const char* firstName, co
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
)"; )";
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
std::string firstName = AUTOU16TOU8(save->szFirstName);
std::string lastName = AUTOU16TOU8(save->szLastName);
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, accountId); sqlite3_bind_int(stmt, 1, AccountID);
sqlite3_bind_int(stmt, 2, slot); sqlite3_bind_int(stmt, 2, save->iSlotNum);
sqlite3_bind_text(stmt, 3, firstName, -1, NULL); sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 4, lastName, -1, NULL); sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL);
sqlite3_bind_int(stmt, 5, settings::SPAWN_X); sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
sqlite3_bind_int(stmt, 6, settings::SPAWN_Y); sqlite3_bind_int(stmt, 6, settings::SPAWN_Y);
sqlite3_bind_int(stmt, 7, settings::SPAWN_Z); sqlite3_bind_int(stmt, 7, settings::SPAWN_Z);
sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE); sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE);
sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1)); sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1));
// if FNCode isn't 0, it's a wheel name
int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
sqlite3_bind_int(stmt, 10, nameCheck); sqlite3_bind_int(stmt, 10, nameCheck);
// blobs // blobs
@@ -645,7 +627,7 @@ void Database::evaluateCustomName(int characterID, CustomName decision) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
bool Database::changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck) { bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"( const char* sql = R"(
@@ -659,10 +641,15 @@ bool Database::changeName(int playerId, int accountId, const char* firstName, co
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, firstName, -1, NULL); std::string firstName = AUTOU16TOU8(save->szFirstName);
sqlite3_bind_text(stmt, 2, lastName, -1, NULL); std::string lastName = AUTOU16TOU8(save->szLastName);
sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, NULL);
// if FNCode isn't 0, it's a wheel name
int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
sqlite3_bind_int(stmt, 3, nameCheck); sqlite3_bind_int(stmt, 3, nameCheck);
sqlite3_bind_int(stmt, 4, playerId); sqlite3_bind_int(stmt, 4, save->iPCUID);
sqlite3_bind_int(stmt, 5, accountId); sqlite3_bind_int(stmt, 5, accountId);
int rc = sqlite3_step(stmt); int rc = sqlite3_step(stmt);

View File

@@ -2,6 +2,40 @@
// Loading and saving players to/from the DB // Loading and saving players to/from the DB
static void removeExpiredVehicles(Player* player) {
int32_t currentTime = getTimestamp();
// if there are expired vehicles in bank just remove them silently
for (int i = 0; i < ABANK_COUNT; i++) {
if (player->Bank[i].iType == 10 && player->Bank[i].iTimeLimit < currentTime && player->Bank[i].iTimeLimit != 0) {
memset(&player->Bank[i], 0, sizeof(sItemBase));
}
}
// we want to leave only 1 expired vehicle on player to delete it with the client packet
std::vector<sItemBase*> toRemove;
// equipped vehicle
if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime && player->Equip[8].iTimeLimit != 0) {
toRemove.push_back(&player->Equip[8]);
player->toRemoveVehicle.eIL = 0;
player->toRemoveVehicle.iSlotNum = 8;
}
// inventory
for (int i = 0; i < AINVEN_COUNT; i++) {
if (player->Inven[i].iType == 10 && player->Inven[i].iTimeLimit < currentTime && player->Inven[i].iTimeLimit != 0) {
toRemove.push_back(&player->Inven[i]);
player->toRemoveVehicle.eIL = 1;
player->toRemoveVehicle.iSlotNum = i;
}
}
// delete all but one vehicles, leave last one for ceremonial deletion
for (int i = 0; i < (int)toRemove.size()-1; i++) {
memset(toRemove[i], 0, sizeof(sItemBase));
}
}
void Database::getPlayer(Player* plr, int id) { void Database::getPlayer(Player* plr, int id) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
@@ -59,13 +93,13 @@ void Database::getPlayer(Player* plr, int id) {
plr->angle = sqlite3_column_int(stmt, 15); plr->angle = sqlite3_column_int(stmt, 15);
plr->HP = sqlite3_column_int(stmt, 16); plr->HP = sqlite3_column_int(stmt, 16);
plr->accountLevel = sqlite3_column_int(stmt, 17); plr->accountLevel = sqlite3_column_int(stmt, 17);
plr->setCapped(CappedValueType::FUSIONMATTER, sqlite3_column_int(stmt, 18)); plr->fusionmatter = sqlite3_column_int(stmt, 18);
plr->setCapped(CappedValueType::TAROS, sqlite3_column_int(stmt, 19)); plr->money = sqlite3_column_int(stmt, 19);
memcpy(plr->aQuestFlag, sqlite3_column_blob(stmt, 20), sizeof(plr->aQuestFlag)); memcpy(plr->aQuestFlag, sqlite3_column_blob(stmt, 20), sizeof(plr->aQuestFlag));
plr->setCapped(CappedValueType::BATTERY_W, sqlite3_column_int(stmt, 21)); plr->batteryW = sqlite3_column_int(stmt, 21);
plr->setCapped(CappedValueType::BATTERY_N, sqlite3_column_int(stmt, 22)); plr->batteryN = sqlite3_column_int(stmt, 22);
plr->mentor = sqlite3_column_int(stmt, 23); plr->mentor = sqlite3_column_int(stmt, 23);
plr->iWarpLocationFlag = sqlite3_column_int(stmt, 24); plr->iWarpLocationFlag = sqlite3_column_int(stmt, 24);
@@ -126,6 +160,8 @@ void Database::getPlayer(Player* plr, int id) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
removeExpiredVehicles(plr);
// get quest inventory // get quest inventory
sql = R"( sql = R"(
SELECT Slot, ID, Opt SELECT Slot, ID, Opt

View File

@@ -1,5 +1,4 @@
#include "servers/CNLoginServer.hpp" #include "servers/CNLoginServer.hpp"
#include "servers/Monitor.hpp"
#include "core/CNShared.hpp" #include "core/CNShared.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"
@@ -13,12 +12,6 @@
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
namespace LoginServer {
std::vector<std::string> WheelFirstNames;
std::vector<std::string> WheelMiddleNames;
std::vector<std::string> WheelLastNames;
}
CNLoginServer::CNLoginServer(uint16_t p) { CNLoginServer::CNLoginServer(uint16_t p) {
serverType = "login"; serverType = "login";
port = p; port = p;
@@ -292,30 +285,11 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp); INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
int errorCode = 0; int errorCode = 0;
if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
std::string firstName = AUTOU16TOU8(save->szFirstName);
std::string lastName = AUTOU16TOU8(save->szLastName);
int nameCheck = 0;
// if FNCode isn't 0, it's a wheel name
if (save->iFNCode != 0) {
if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) {
errorCode = 4; errorCode = 4;
} else { } else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
nameCheck = settings:: APPROVEWHEELNAMES ? 1 : 0;
}
} else {
// custom name
nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0;
}
if (errorCode == 0) {
if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) {
errorCode = 4;
} else if (!Database::isNameFree(firstName, lastName)) {
errorCode = 1; errorCode = 1;
} }
}
if (errorCode != 0) { if (errorCode != 0) {
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp); INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp);
@@ -332,19 +306,12 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum)) if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum))
return invalidCharacter(sock); return invalidCharacter(sock);
resp.iPC_UID = Database::createCharacter(save->iSlotNum, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck); resp.iPC_UID = Database::createCharacter(save, loginSessions[sock].userID);
// if query somehow failed // if query somehow failed
if (resp.iPC_UID == 0) { if (resp.iPC_UID == 0) {
std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl; std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl;
return invalidCharacter(sock); return invalidCharacter(sock);
} }
// fire name check event if needed
if (nameCheck != 1) {
std::string namereq = std::to_string(resp.iPC_UID) + " " + firstName + " " + lastName;
Monitor::namereqs.push_back(namereq);
}
resp.iSlotNum = save->iSlotNum; resp.iSlotNum = save->iSlotNum;
resp.iGender = save->iGender; resp.iGender = save->iGender;
@@ -360,9 +327,7 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
DEBUGLOG( DEBUGLOG(
std::cout << "Login Server: new character created" << std::endl; std::cout << "Login Server: new character created" << std::endl;
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl; std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
std::cout << "\tName: " << firstName << " " << lastName; std::cout << "\tName: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
if (nameCheck != 1) std::cout << " (pending approval)";
std::cout << std::endl;
) )
} }
@@ -529,31 +494,12 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf; auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
int errorCode = 0; int errorCode = 0;
if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
std::string firstName = AUTOU16TOU8(save->szFirstName);
std::string lastName = AUTOU16TOU8(save->szLastName);
int nameCheck = 0;
// if FNCode isn't 0, it's a wheel name
if (save->iFNCode != 0) {
if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) {
errorCode = 4;
} else {
nameCheck = settings::APPROVEWHEELNAMES ? 1 : 0;
}
} else {
// custom name
nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0;
}
if (errorCode == 0) {
if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) {
errorCode = 4; errorCode = 4;
} }
else if (!Database::isNameFree(firstName, lastName)) { else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
errorCode = 1; errorCode = 1;
} }
}
if (errorCode != 0) { if (errorCode != 0) {
INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp); INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp);
@@ -567,15 +513,9 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
return; return;
} }
if (!Database::changeName(save->iPCUID, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck)) if (!Database::changeName(save, loginSessions[sock].userID))
return invalidCharacter(sock); return invalidCharacter(sock);
// fire name check event if needed
if (nameCheck != 1) {
std::string namereq = std::to_string(save->iPCUID) + " " + firstName + " " + lastName;
Monitor::namereqs.push_back(namereq);
}
INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp); INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp);
resp.iPC_UID = save->iPCUID; resp.iPC_UID = save->iPCUID;
memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName)); memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName));
@@ -587,10 +527,8 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC); sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC);
DEBUGLOG( DEBUGLOG(
std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl; std::cout << "Login Server: Name check success for character [" << save->iPCUID << "]" << std::endl;
std::cout << "\tNew name: " << firstName << " " << lastName; std::cout << "\tNew name: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
if (nameCheck != 1) std::cout << " (pending approval)";
std::cout << std::endl;
) )
} }
@@ -683,42 +621,6 @@ bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tr
return BCrypt::validatePassword(tryPassword, actualPassword); return BCrypt::validatePassword(tryPassword, actualPassword);
} }
bool CNLoginServer::isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std::string& firstName, std::string& lastName) {
if (fnCode >= LoginServer::WheelFirstNames.size()
|| mnCode >= LoginServer::WheelMiddleNames.size()
|| lnCode >= LoginServer::WheelLastNames.size()) {
std::cout << "[WARN] Login Server: Invalid name codes received: " << fnCode << " " << mnCode << " " << lnCode << std::endl;
return false;
}
// client sends 1 if not selected for these. they point to a single blank space. why.
// just change them to 0, which points to an empty string; keeps the code much cleaner
if (mnCode == 1) mnCode = 0;
if (lnCode == 1) lnCode = 0;
std::string firstNameFromWheel = LoginServer::WheelFirstNames[fnCode];
std::string middleNamePart = LoginServer::WheelMiddleNames[mnCode];
std::string lastNamePart = LoginServer::WheelLastNames[lnCode];
if (mnCode != 0 && middleNamePart[middleNamePart.size() - 1] != ' ') {
// If there's a middle name, we need to lowercase the last name
std::transform(lastNamePart.begin(), lastNamePart.end(), lastNamePart.begin(), ::tolower);
}
std::string lastNameFromWheel = middleNamePart + lastNamePart;
if (firstNameFromWheel.empty() || lastNameFromWheel.empty()) {
std::cout << "[WARN] Login Server: Invalid wheel name combo: " << fnCode << " " << mnCode << " " << lnCode << std::endl;
return false;
}
if (firstName != firstNameFromWheel || lastName != lastNameFromWheel) {
std::cout << "[WARN] Login Server: Name wheel mismatch. Expected " << firstNameFromWheel << " " << lastNameFromWheel << ", got " << firstName << " " << lastName << std::endl;
return false;
}
return true;
}
bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) { bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) {
//Allow alphanumeric and dot characters in names(disallows dot and space characters at the beginning of a name) //Allow alphanumeric and dot characters in names(disallows dot and space characters at the beginning of a name)
std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)");
@@ -796,7 +698,7 @@ bool CNLoginServer::checkToken(CNSocket* sock, Database::Account& account, std::
} }
} }
// cookie check disabled or failed; check to see if it's a plaintext password // cookie check failed; check to see if it's a plaintext password sent by auto-login
if (CNLoginServer::isAuthMethodAllowed(AuthMethod::PASSWORD) if (CNLoginServer::isAuthMethodAllowed(AuthMethod::PASSWORD)
&& CNLoginServer::isPasswordCorrect(account.Password, token)) { && CNLoginServer::isPasswordCorrect(account.Password, token)) {
return true; return true;

View File

@@ -7,12 +7,6 @@
#include <map> #include <map>
namespace LoginServer {
extern std::vector<std::string> WheelFirstNames;
extern std::vector<std::string> WheelMiddleNames;
extern std::vector<std::string> WheelLastNames;
}
struct CNLoginData { struct CNLoginData {
int userID; int userID;
time_t lastHeartbeat; time_t lastHeartbeat;
@@ -57,7 +51,6 @@ private:
static bool isPasswordGood(std::string& password); static bool isPasswordGood(std::string& password);
static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword); static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword);
static bool isAccountInUse(int accountId); static bool isAccountInUse(int accountId);
static bool isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std::string& firstName, std::string& lastName);
static bool isCharacterNameGood(std::string Firstname, std::string Lastname); static bool isCharacterNameGood(std::string Firstname, std::string Lastname);
static bool isAuthMethodAllowed(AuthMethod authMethod); static bool isAuthMethodAllowed(AuthMethod authMethod);
static bool checkUsername(CNSocket* sock, std::string& username); static bool checkUsername(CNSocket* sock, std::string& username);

View File

@@ -9,7 +9,6 @@
#include "MobAI.hpp" #include "MobAI.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "TableData.hpp" // for flush() #include "TableData.hpp" // for flush()
#include "Items.hpp" // for checkAndRemoveExpiredItems()
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
@@ -24,7 +23,6 @@ CNShardServer::CNShardServer(uint16_t p) {
pHandler = &CNShardServer::handlePacket; pHandler = &CNShardServer::handlePacket;
REGISTER_SHARD_TIMER(keepAliveTimer, 4000); REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
REGISTER_SHARD_TIMER(periodicSaveTimer, settings::DBSAVEINTERVAL*1000); REGISTER_SHARD_TIMER(periodicSaveTimer, settings::DBSAVEINTERVAL*1000);
REGISTER_SHARD_TIMER(periodicItemExpireTimer, 60000);
init(); init();
if (settings::MONITORENABLED) if (settings::MONITORENABLED)
@@ -90,22 +88,6 @@ void CNShardServer::periodicSaveTimer(CNServer* serv, time_t currTime) {
std::cout << "[INFO] Done." << std::endl; std::cout << "[INFO] Done." << std::endl;
} }
void CNShardServer::periodicItemExpireTimer(CNServer* serv, time_t currTime) {
size_t playersWithExpiredItems = 0;
size_t itemsRemoved = 0;
for (const auto& [sock, player] : PlayerManager::players) {
// check and remove expired items
size_t removed = Items::checkAndRemoveExpiredItems(sock, player);
itemsRemoved += removed;
playersWithExpiredItems += (removed == 0 ? 0 : 1);
}
if (playersWithExpiredItems > 0) {
std::cout << "[INFO] Removed " << itemsRemoved << " expired items from " << playersWithExpiredItems << " players." << std::endl;
}
}
bool CNShardServer::checkExtraSockets(int i) { bool CNShardServer::checkExtraSockets(int i) {
return Monitor::acceptConnection(fds[i].fd, fds[i].revents); return Monitor::acceptConnection(fds[i].fd, fds[i].revents);
} }

View File

@@ -16,7 +16,6 @@ private:
static void keepAliveTimer(CNServer*, time_t); static void keepAliveTimer(CNServer*, time_t);
static void periodicSaveTimer(CNServer* serv, time_t currTime); static void periodicSaveTimer(CNServer* serv, time_t currTime);
static void periodicItemExpireTimer(CNServer* serv, time_t currTime);
public: public:
static std::map<uint32_t, PacketHandler> ShardPackets; static std::map<uint32_t, PacketHandler> ShardPackets;

View File

@@ -14,13 +14,6 @@ static std::mutex sockLock; // guards socket list
static std::list<SOCKET> sockets; static std::list<SOCKET> sockets;
static sockaddr_in address; static sockaddr_in address;
std::vector<std::string> Monitor::chats;
std::vector<std::string> Monitor::bcasts;
std::vector<std::string> Monitor::emails;
std::vector<std::string> Monitor::namereqs;
using namespace Monitor;
static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) { static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) {
int n = 0; int n = 0;
int sock = *it; int sock = *it;
@@ -106,23 +99,15 @@ outer:
} }
// chat // chat
for (auto& str : chats) { for (auto& str : Chat::dump) {
n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str()); n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str());
if (!transmit(it, buff, n)) if (!transmit(it, buff, n))
goto outer; goto outer;
} }
// announcements
for (auto& str : bcasts) {
n = std::snprintf(buff, sizeof(buff), "bcast %s\n", str.c_str());
if (!transmit(it, buff, n))
goto outer;
}
// emails // emails
for (auto& str : emails) { for (auto& str : Email::dump) {
n = process_email(buff, str); n = process_email(buff, str);
if (!transmit(it, buff, n)) if (!transmit(it, buff, n))
@@ -132,24 +117,14 @@ outer:
goto outer; goto outer;
} }
// name requests
for (auto& str : namereqs) {
n = std::snprintf(buff, sizeof(buff), "namereq %s\n", str.c_str());
if (!transmit(it, buff, n))
goto outer;
}
if (!transmit(it, (char*)"end\n", 4)) if (!transmit(it, (char*)"end\n", 4))
continue; continue;
it++; it++;
} }
chats.clear(); Chat::dump.clear();
bcasts.clear(); Email::dump.clear();
emails.clear();
namereqs.clear();
} }
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) { bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {

View File

@@ -3,11 +3,6 @@
#include "core/Core.hpp" #include "core/Core.hpp"
namespace Monitor { namespace Monitor {
extern std::vector<std::string> chats;
extern std::vector<std::string> bcasts;
extern std::vector<std::string> emails;
extern std::vector<std::string> namereqs;
SOCKET init(); SOCKET init();
bool acceptConnection(SOCKET, uint16_t); bool acceptConnection(SOCKET, uint16_t);
}; };

View File

@@ -12,8 +12,7 @@ bool settings::SANDBOX = true;
std::string settings::SANDBOXEXTRAPATH = ""; std::string settings::SANDBOXEXTRAPATH = "";
int settings::LOGINPORT = 23000; int settings::LOGINPORT = 23000;
bool settings::APPROVEWHEELNAMES = true; bool settings::APPROVEALLNAMES = true;
bool settings::APPROVECUSTOMNAMES = true;
bool settings::AUTOCREATEACCOUNTS = true; bool settings::AUTOCREATEACCOUNTS = true;
std::string settings::AUTHMETHODS = "password"; std::string settings::AUTHMETHODS = "password";
int settings::DBSAVEINTERVAL = 240; int settings::DBSAVEINTERVAL = 240;
@@ -74,19 +73,6 @@ int settings::EVENTMODE = 0;
// race settings // race settings
bool settings::IZRACESCORECAPPED = true; bool settings::IZRACESCORECAPPED = true;
// drop fixes enabled
bool settings::DROPFIXESENABLED = false;
// less taro / fm while in a group
bool settings::LESSTAROFMINGROUPDISABLED = false;
// general reward percentages
int settings::TARORATE = 100;
int settings::FUSIONMATTERRATE = 100;
// should expired items in the bank disappear automatically?
bool settings::REMOVEEXPIREDITEMSFROMBANK = false;
void settings::init() { void settings::init() {
INIReader reader("config.ini"); INIReader reader("config.ini");
@@ -103,8 +89,7 @@ void settings::init() {
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX); SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH); SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH);
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
APPROVEWHEELNAMES = reader.GetBoolean("login", "acceptallwheelnames", APPROVEWHEELNAMES); APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES);
APPROVECUSTOMNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVECUSTOMNAMES);
AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS); AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS);
AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS); AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS);
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
@@ -130,30 +115,13 @@ void settings::init() {
TDATADIR = reader.Get("shard", "tdatadir", TDATADIR); TDATADIR = reader.Get("shard", "tdatadir", TDATADIR);
PATCHDIR = reader.Get("shard", "patchdir", PATCHDIR); PATCHDIR = reader.Get("shard", "patchdir", PATCHDIR);
ENABLEDPATCHES = reader.Get("shard", "enabledpatches", ENABLEDPATCHES); ENABLEDPATCHES = reader.Get("shard", "enabledpatches", ENABLEDPATCHES);
DROPFIXESENABLED = reader.GetBoolean("shard", "dropfixesenabled", DROPFIXESENABLED);
LESSTAROFMINGROUPDISABLED = reader.GetBoolean("shard", "lesstarofmingroupdisabled", LESSTAROFMINGROUPDISABLED);
TARORATE = reader.GetInteger("shard", "tarorate", TARORATE);
FUSIONMATTERRATE = reader.GetInteger("shard", "fusionmatterrate", FUSIONMATTERRATE);
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); ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
IZRACESCORECAPPED = reader.GetBoolean("shard", "izracescorecapped", IZRACESCORECAPPED); IZRACESCORECAPPED = reader.GetBoolean("shard", "izracescorecapped", IZRACESCORECAPPED);
REMOVEEXPIREDITEMSFROMBANK = reader.GetBoolean("shard", "removeexpireditemsfrombank", REMOVEEXPIREDITEMSFROMBANK);
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED); MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT); MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
MONITORLISTENIP = reader.Get("monitor", "listenip", MONITORLISTENIP); MONITORLISTENIP = reader.Get("monitor", "listenip", MONITORLISTENIP);
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL); MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
if (DROPFIXESENABLED) {
std::cout << "[INFO] Drop fixes enabled" << std::endl;
if (ENABLEDPATCHES.empty()) {
ENABLEDPATCHES = "0104-fixes";
} else {
ENABLEDPATCHES += " 0104-fixes";
if (ENABLEDPATCHES.find("1013") != std::string::npos) {
ENABLEDPATCHES += " 1013-fixes";
}
}
}
} }

View File

@@ -9,8 +9,7 @@ namespace settings {
extern bool SANDBOX; extern bool SANDBOX;
extern std::string SANDBOXEXTRAPATH; extern std::string SANDBOXEXTRAPATH;
extern int LOGINPORT; extern int LOGINPORT;
extern bool APPROVEWHEELNAMES; extern bool APPROVEALLNAMES;
extern bool APPROVECUSTOMNAMES;
extern bool AUTOCREATEACCOUNTS; extern bool AUTOCREATEACCOUNTS;
extern std::string AUTHMETHODS; extern std::string AUTHMETHODS;
extern int DBSAVEINTERVAL; extern int DBSAVEINTERVAL;
@@ -45,11 +44,6 @@ namespace settings {
extern int MONITORINTERVAL; extern int MONITORINTERVAL;
extern bool DISABLEFIRSTUSEFLAG; extern bool DISABLEFIRSTUSEFLAG;
extern bool IZRACESCORECAPPED; extern bool IZRACESCORECAPPED;
extern bool DROPFIXESENABLED;
extern bool LESSTAROFMINGROUPDISABLED;
extern int TARORATE;
extern int FUSIONMATTERRATE;
extern bool REMOVEEXPIREDITEMSFROMBANK;
void init(); void init();
} }

2
tdata

Submodule tdata updated: fed031b972...bdb611b092

View File

@@ -1,52 +0,0 @@
# Run this first if needed:
# Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
param (
# height of largest column without top bar
[Parameter(Mandatory=$true)]
[string]$protocolVersion
)
$ErrorActionPreference = 'Stop'
# check for vscmd
if ([string]::IsNullOrEmpty($env:VSCMD_VER)) {
Write-Host 'Must be run inside of VS Developer Powershell'
exit 1
}
# check for git
try {
$git_version = git --version
} catch {
Write-Host 'git not installed'
exit 1
}
# setup vcpkg
if (Test-Path -Path 'vcpkg\') {
Write-Host 'vcpkg already setup'
} else {
Write-Host 'Setting up vcpkg...'
git clone "https://github.com/microsoft/vcpkg.git"
}
if (-not (Test-Path -Path 'vcpkg\vcpkg.exe')) {
Write-Host 'Bootstrapping vcpkg...'
Start-Process -Wait -NoNewWindow -FilePath 'vcpkg\bootstrap-vcpkg.bat' -ArgumentList '-disableMetrics'
}
$env:VCPKG_ROOT='' # ignore msvc's vcpkg root, it doesn't work
$vcpkg = (Resolve-Path -Path '.\vcpkg')
Write-Host "vcpkg installed to $vcpkg"
Start-Process -Wait -NoNewWindow -FilePath 'vcpkg\vcpkg.exe' -ArgumentList 'install sqlite3:x64-windows'
# setup cmake project
if (Test-Path -Path 'build\') {
Write-Host 'cmake project already setup';
} else {
Write-Host 'Setting up cmake project...'
Start-Process -Wait -NoNewWindow -FilePath 'cmake' -ArgumentList "-B build -DPROTOCOL_VERSION=$protocolVersion -DCMAKE_TOOLCHAIN_FILE=$vcpkg\scripts\buildsystems\vcpkg.cmake"
}
Write-Host 'Done!'
$sln = (Resolve-Path -Path '.\build\OpenFusion.sln')
Write-Host "Solution file is at $sln"