mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2026-03-28 15:40:03 +00:00
Compare commits
20 Commits
db
...
06d1ccc17e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06d1ccc17e | ||
|
|
113bc0bc1b | ||
|
9a62ec61c9
|
|||
|
|
51d3cfbb3b | ||
|
|
8df263a64d | ||
| 572df90645 | |||
|
|
8d0587dd45 | ||
|
|
0078be8e9a | ||
|
b617456aa1
|
|||
|
935ee1bf6f
|
|||
|
43a2504357
|
|||
|
ca196bf620
|
|||
|
6b9ae4c325
|
|||
|
d06c324aa3
|
|||
|
052196d1cd
|
|||
|
e84f6505b8
|
|||
|
b483bf7190
|
|||
|
6ff51685a8
|
|||
|
b4ed31d4fb
|
|||
| 36e0667ed2 |
14
.github/workflows/check-builds.yaml
vendored
14
.github/workflows/check-builds.yaml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
- .github/workflows/check-builds.yaml
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
- tdata
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
@@ -19,7 +20,7 @@ on:
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Set environment
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
@@ -48,6 +49,7 @@ jobs:
|
||||
Rename-Item -Path "bin/fusion" -newName "$version-fusion"
|
||||
Write-Output "Built version $version"
|
||||
}
|
||||
Copy-Item -Path "tdata" -Destination "bin/tdata" -Recurse
|
||||
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
|
||||
Copy-Item -Path "config.ini" -Destination "bin"
|
||||
shell: pwsh
|
||||
@@ -58,7 +60,7 @@ jobs:
|
||||
path: bin
|
||||
|
||||
windows-build:
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Set environment
|
||||
run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV
|
||||
@@ -73,7 +75,7 @@ jobs:
|
||||
$configurations = "Release"
|
||||
# "Debug" builds are disabled, since we don't really need them
|
||||
|
||||
$vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise"
|
||||
$vsPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise"
|
||||
|
||||
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
||||
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
|
||||
@@ -100,6 +102,7 @@ jobs:
|
||||
}
|
||||
Rename-Item -Path "bin/$configuration" -newName "$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 "config.ini" -Destination "bin/$version-$configuration"
|
||||
}
|
||||
@@ -108,11 +111,11 @@ jobs:
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}'
|
||||
name: 'windows-vs2022-bin-x64-${{ env.SHORT_SHA }}'
|
||||
path: bin
|
||||
|
||||
copy-artifacts:
|
||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
||||
if: github.event_name != 'pull_request' && (github.ref_type == 'tag' || github.ref_name == 'master')
|
||||
runs-on: ubuntu-latest
|
||||
needs: [windows-build, ubuntu-build]
|
||||
env:
|
||||
@@ -121,7 +124,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- run: |
|
||||
GITDESC=$(git describe --tags)
|
||||
|
||||
@@ -38,4 +38,4 @@ EXPOSE 23000/tcp
|
||||
EXPOSE 23001/tcp
|
||||
EXPOSE 8003/tcp
|
||||
|
||||
LABEL Name=openfusion Version=1.6.0
|
||||
LABEL Name=openfusion Version=2.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2024 OpenFusion Contributors
|
||||
Copyright (c) 2020-2025 OpenFusion Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
10
README.md
10
README.md
@@ -14,22 +14,22 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
|
||||
|
||||
### Getting Started
|
||||
#### Method A: Installer (Easiest)
|
||||
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 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
#### Method B: Standalone .zip file
|
||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.6/OpenFusionClient-1.6.zip).
|
||||
1. Download the launcher from [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Portable.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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
1. Grab `OpenFusionServer-1.6-Original.zip` or `OpenFusionServer-1.6-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.6).
|
||||
1. Grab `OpenFusionServer-Windows-Original.zip` or `OpenFusionServer-Windows-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/latest).
|
||||
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:
|
||||
1. For Description, enter anything you want. This is what will show up in the server list.
|
||||
|
||||
32
config.ini
32
config.ini
@@ -12,6 +12,8 @@ sandbox=true
|
||||
[login]
|
||||
# must be kept in sync with loginInfo.php
|
||||
port=23000
|
||||
# will all name wheel names be approved instantly?
|
||||
acceptallwheelnames=true
|
||||
# will all custom names be approved instantly?
|
||||
acceptallcustomnames=true
|
||||
# should attempts to log into non-existent accounts
|
||||
@@ -41,6 +43,33 @@ simulatemobs=true
|
||||
# little message players see when they enter the game
|
||||
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
|
||||
# requires to run. You can override them by changing their values and
|
||||
# uncommenting them (removing the leading # character from that line).
|
||||
@@ -70,9 +99,6 @@ motd=Welcome to OpenFusion!
|
||||
# location of the database
|
||||
#dbpath=database.db
|
||||
|
||||
# should there be a score cap for infected zone races?
|
||||
#izracescorecapped=true
|
||||
|
||||
# should tutorial flags be disabled off the bat?
|
||||
disablefirstuseflag=true
|
||||
|
||||
|
||||
8
sql/migration5.sql
Normal file
8
sql/migration5.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
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;
|
||||
@@ -1,14 +1,16 @@
|
||||
CREATE TABLE IF NOT EXISTS Accounts (
|
||||
AccountID INTEGER NOT NULL,
|
||||
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||||
Password TEXT NOT NULL,
|
||||
Selected INTEGER DEFAULT 1 NOT NULL,
|
||||
AccountLevel INTEGER NOT NULL,
|
||||
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
BannedUntil INTEGER DEFAULT 0 NOT NULL,
|
||||
BannedSince INTEGER DEFAULT 0 NOT NULL,
|
||||
BanReason TEXT DEFAULT '' NOT NULL,
|
||||
AccountID INTEGER NOT NULL,
|
||||
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
|
||||
Password TEXT NOT NULL,
|
||||
Selected INTEGER DEFAULT 1 NOT NULL,
|
||||
AccountLevel INTEGER NOT NULL,
|
||||
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||
BannedUntil INTEGER DEFAULT 0 NOT NULL,
|
||||
BannedSince INTEGER DEFAULT 0 NOT NULL,
|
||||
BanReason TEXT DEFAULT '' NOT NULL,
|
||||
Email TEXT DEFAULT '' NOT NULL,
|
||||
LastPasswordReset INTEGER DEFAULT 0 NOT NULL,
|
||||
PRIMARY KEY(AccountID AUTOINCREMENT)
|
||||
);
|
||||
|
||||
|
||||
@@ -170,11 +170,11 @@ static SkillResult handleSkillBatteryDrain(SkillData* skill, int power, ICombata
|
||||
if(!blocked) {
|
||||
boostDrain = (int)(skill->values[0][power] * scalingFactor);
|
||||
if(boostDrain > plr->batteryW) boostDrain = plr->batteryW;
|
||||
plr->batteryW -= boostDrain;
|
||||
plr->subtractCapped(CappedValueType::BATTERY_W, boostDrain);
|
||||
|
||||
potionDrain = (int)(skill->values[1][power] * scalingFactor);
|
||||
if(potionDrain > plr->batteryN) potionDrain = plr->batteryN;
|
||||
plr->batteryN -= potionDrain;
|
||||
plr->subtractCapped(CappedValueType::BATTERY_N, potionDrain);
|
||||
}
|
||||
|
||||
sSkillResult_BatteryDrain result{};
|
||||
@@ -364,7 +364,7 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
|
||||
ICombatant* src = nullptr;
|
||||
if(npc.kind == EntityKind::COMBAT_NPC || npc.kind == EntityKind::MOB)
|
||||
src = dynamic_cast<ICombatant*>(entity);
|
||||
|
||||
|
||||
SkillData* skill = &SkillTable[skillID];
|
||||
|
||||
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||
@@ -443,7 +443,7 @@ std::vector<ICombatant*> Abilities::matchTargets(ICombatant* src, SkillData* ski
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
return targets;
|
||||
}
|
||||
|
||||
/* ripped from client (enums emplaced) */
|
||||
|
||||
@@ -75,30 +75,22 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
case CN_GM_SET_VALUE_TYPE__HP:
|
||||
response.iSetValue = plr->HP = setData->iSetValue;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY :
|
||||
plr->batteryW = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
|
||||
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY:
|
||||
plr->setCapped(CappedValueType::BATTERY_W, setData->iSetValue);
|
||||
response.iSetValue = plr->batteryW;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__NANO_BATTERY:
|
||||
plr->batteryN = setData->iSetValue;
|
||||
|
||||
// caps
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
|
||||
plr->setCapped(CappedValueType::BATTERY_N, setData->iSetValue);
|
||||
response.iSetValue = plr->batteryN;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__FUSION_MATTER:
|
||||
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
||||
plr->setCapped(CappedValueType::FUSIONMATTER, setData->iSetValue);
|
||||
Missions::updateFusionMatter(sock);
|
||||
response.iSetValue = plr->fusionmatter;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__CANDY:
|
||||
response.iSetValue = plr->money = setData->iSetValue;
|
||||
plr->setCapped(CappedValueType::TAROS, setData->iSetValue);
|
||||
response.iSetValue = plr->money;
|
||||
break;
|
||||
case CN_GM_SET_VALUE_TYPE__SPEED:
|
||||
case CN_GM_SET_VALUE_TYPE__JUMP:
|
||||
@@ -148,6 +140,60 @@ 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
|
||||
}
|
||||
|
||||
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) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
@@ -371,6 +417,7 @@ void BuiltinCommands::init() {
|
||||
|
||||
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_REWARD_RATE, setGMRewardRate);
|
||||
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_LOCATION, locatePlayer);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_KICK_PLAYER, kickPlayer);
|
||||
|
||||
32
src/Chat.cpp
32
src/Chat.cpp
@@ -1,6 +1,7 @@
|
||||
#include "Chat.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
@@ -8,8 +9,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
std::vector<std::string> Chat::dump;
|
||||
|
||||
using namespace Chat;
|
||||
|
||||
static void chatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
@@ -28,7 +27,7 @@ static void chatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
|
||||
@@ -51,7 +50,7 @@ static void menuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
@@ -103,14 +102,14 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
|
||||
memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
||||
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) {
|
||||
case 0: // area (all players in viewable chunks)
|
||||
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
break;
|
||||
case 1: // shard
|
||||
case 2: // world
|
||||
break; // not applicable to OpenFusion
|
||||
case 1: // channel
|
||||
case 2: // shard
|
||||
case 3: // global (all players)
|
||||
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
|
||||
CNSocket* allSock = it->first;
|
||||
@@ -120,9 +119,12 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::string logLine = "[Bcast " + std::to_string(announcement->iAreaType) + "] " + PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back("**" + logLine + "**");
|
||||
std::string logLine = std::to_string(announcement->iAreaType) + " "
|
||||
+ std::to_string(announcement->iAnnounceType) + " "
|
||||
+ std::to_string(announcement->iDuringTime) + " "
|
||||
+ PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
|
||||
std::cout << "Broadcast " << logLine << std::endl;
|
||||
Monitor::bcasts.push_back(logLine);
|
||||
}
|
||||
|
||||
// Buddy freechatting
|
||||
@@ -155,7 +157,7 @@ static void buddyChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
|
||||
@@ -185,7 +187,7 @@ static void buddyMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||
|
||||
@@ -218,7 +220,7 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
resp.iEmoteCode = pacdat->iEmoteCode;
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT);
|
||||
@@ -241,7 +243,7 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
|
||||
@@ -264,7 +266,7 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <vector>
|
||||
|
||||
namespace Chat {
|
||||
extern std::vector<std::string> dump;
|
||||
void init();
|
||||
|
||||
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD
|
||||
|
||||
@@ -300,7 +300,7 @@ EntityRef CombatNPC::getRef() {
|
||||
}
|
||||
|
||||
void CombatNPC::step(time_t currTime) {
|
||||
|
||||
|
||||
if(stateHandlers.find(state) != stateHandlers.end())
|
||||
stateHandlers[state](this, currTime);
|
||||
else {
|
||||
@@ -441,11 +441,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
|
||||
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||
|
||||
if (plr->batteryW >= 6 + difficulty)
|
||||
plr->batteryW -= 6 + difficulty;
|
||||
else
|
||||
plr->batteryW = 0;
|
||||
|
||||
plr->subtractCapped(CappedValueType::BATTERY_W, 6 + difficulty);
|
||||
damage.first = mob->takeDamage(sock, damage.first);
|
||||
|
||||
respdata[i].iID = mob->id;
|
||||
@@ -690,10 +686,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
Nanos::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
|
||||
}
|
||||
|
||||
if (plr->batteryW >= 6 + plr->level)
|
||||
plr->batteryW -= 6 + plr->level;
|
||||
else
|
||||
plr->batteryW = 0;
|
||||
plr->subtractCapped(CappedValueType::BATTERY_W, 6 + plr->level);
|
||||
|
||||
damage.first = target->takeDamage(sock, damage.first);
|
||||
|
||||
@@ -742,7 +735,7 @@ static int8_t addBullet(Player* plr, bool isGrenade) {
|
||||
toAdd.weaponBoost = plr->batteryW > 0;
|
||||
if (toAdd.weaponBoost) {
|
||||
int boostCost = Rand::rand(11) + 20;
|
||||
plr->batteryW = boostCost > plr->batteryW ? 0 : plr->batteryW - boostCost;
|
||||
plr->subtractCapped(CappedValueType::BATTERY_W, boostCost);
|
||||
}
|
||||
|
||||
Bullets[plr->iID][findId] = toAdd;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "core/Core.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
@@ -10,8 +11,6 @@
|
||||
|
||||
using namespace Email;
|
||||
|
||||
std::vector<std::string> Email::dump;
|
||||
|
||||
// New email notification
|
||||
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp);
|
||||
@@ -76,7 +75,7 @@ static void emailReceiveTaros(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
Database::EmailData email = Database::getEmail(plr->iID, pkt->iEmailIndex);
|
||||
// money transfer
|
||||
plr->money += email.Taros;
|
||||
plr->addCapped(CappedValueType::TAROS, email.Taros);
|
||||
email.Taros = 0;
|
||||
// update Taros in email
|
||||
Database::updateEmailContent(&email);
|
||||
@@ -275,7 +274,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
||||
plr->money -= cost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, cost);
|
||||
Database::EmailData email = {
|
||||
(int)pkt->iTo_PCUID, // PlayerId
|
||||
Database::getNextEmailIndex(pkt->iTo_PCUID), // MsgIndex
|
||||
@@ -292,7 +291,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
||||
};
|
||||
|
||||
if (!Database::sendEmail(&email, attachments, plr)) {
|
||||
plr->money += cost; // give money back
|
||||
plr->addCapped(CappedValueType::TAROS, cost); // give money back
|
||||
// give items back
|
||||
while (!attachments.empty()) {
|
||||
sItemBase attachment = attachments.back();
|
||||
@@ -324,7 +323,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::cout << logEmail << std::endl;
|
||||
dump.push_back(logEmail);
|
||||
Monitor::emails.push_back(logEmail);
|
||||
|
||||
// notification to recipient if online
|
||||
CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID);
|
||||
|
||||
@@ -4,7 +4,5 @@
|
||||
#include <string>
|
||||
|
||||
namespace Email {
|
||||
extern std::vector<std::string> dump;
|
||||
|
||||
void init();
|
||||
}
|
||||
|
||||
115
src/Entities.cpp
115
src/Entities.cpp
@@ -117,6 +117,121 @@ sPCAppearanceData Player::getAppearanceData() {
|
||||
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()
|
||||
void Player::enterIntoViewOf(CNSocket *sock) {
|
||||
INITSTRUCT(sP_FE2CL_PC_NEW, pkt);
|
||||
|
||||
381
src/Items.cpp
381
src/Items.cpp
@@ -33,6 +33,19 @@ std::map<int32_t, int32_t> Items::EventToDropMap;
|
||||
std::map<int32_t, int32_t> Items::MobToDropMap;
|
||||
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
|
||||
std::map<int32_t, int32_t> Items::NanoCapsules; // crate id -> nano id
|
||||
|
||||
@@ -322,14 +335,14 @@ static void itemMoveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// if equipping an item, validate that it's of the correct type for the slot
|
||||
if ((SlotType)itemmove->eTo == SlotType::EQUIP) {
|
||||
if (fromItem->iType == 10 && itemmove->iToSlotNum != 8)
|
||||
if (fromItem->iType == 10 && itemmove->iToSlotNum != EQUIP_SLOT_VEHICLE)
|
||||
return; // vehicle in wrong slot
|
||||
else if (fromItem->iType != 10
|
||||
&& !(fromItem->iType == 0 && itemmove->iToSlotNum == 7)
|
||||
&& fromItem->iType != itemmove->iToSlotNum)
|
||||
return; // something other than a vehicle or a weapon in a non-matching slot
|
||||
else if (itemmove->iToSlotNum >= AEQUIP_COUNT) // TODO: reject slots >= 9?
|
||||
return; // invalid slot
|
||||
else if (itemmove->iToSlotNum >= AEQUIP_COUNT_MINUS_BOOSTERS)
|
||||
return; // boosters can't be equipped via move packet
|
||||
}
|
||||
|
||||
// save items to response
|
||||
@@ -386,7 +399,7 @@ static void itemMoveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
// unequip vehicle if equip slot 8 is 0
|
||||
if (plr->Equip[8].iID == 0 && plr->iPCState & 8) {
|
||||
if (plr->Equip[EQUIP_SLOT_VEHICLE].iID == 0 && plr->iPCState & 8) {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
|
||||
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_OFF_SUCC);
|
||||
|
||||
@@ -430,30 +443,19 @@ static void itemDeleteHandler(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket(resp, P_FE2CL_REP_PC_ITEM_DELETE_SUCC);
|
||||
}
|
||||
|
||||
static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
static void useGumball(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
|
||||
|
||||
// gumball can only be used from inventory, so we ignore eIL
|
||||
sItemBase gumball = player->Inven[request->iSlotNum];
|
||||
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
|
||||
int nanoStyle = Nanos::nanoStyle(nano.iID);
|
||||
if (!((gumball.iID == 119 && nanoStyle == 0) ||
|
||||
( gumball.iID == 120 && nanoStyle == 1) ||
|
||||
( gumball.iID == 121 && nanoStyle == 2))) {
|
||||
if (!((gumball.iID == GENERALITEM_GUMBALL_ADAPTIUM && nanoStyle == 0) ||
|
||||
( gumball.iID == GENERALITEM_GUMBALL_BLASTONS && nanoStyle == 1) ||
|
||||
( gumball.iID == GENERALITEM_GUMBALL_COSMIX && nanoStyle == 2))) {
|
||||
std::cout << "[WARN] Gumball type doesn't match nano type" << std::endl;
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_USE_FAIL, response);
|
||||
sock->sendPacket(response, P_FE2CL_REP_PC_ITEM_USE_FAIL);
|
||||
@@ -472,11 +474,8 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gumball.iOpt == 0)
|
||||
gumball = {};
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BODY_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BODY_SIZE);
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
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));
|
||||
@@ -515,6 +514,128 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
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) {
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
@@ -598,7 +719,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 (failing) {
|
||||
item->sItem.iType = 7;
|
||||
item->sItem.iID = 119 + Rand::rand(3);
|
||||
item->sItem.iID = GENERALITEM_GUMBALL_ADAPTIUM + Rand::rand(3);
|
||||
item->sItem.iOpt = 1;
|
||||
|
||||
std::cout << "[WARN] Crate open failed, giving a Gumball..." << std::endl;
|
||||
@@ -632,39 +753,89 @@ Item* Items::getItemData(int32_t id, int32_t type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Items::checkItemExpire(CNSocket* sock, Player* player) {
|
||||
if (player->toRemoveVehicle.eIL == 0 && player->toRemoveVehicle.iSlotNum == 0)
|
||||
return;
|
||||
size_t Items::checkAndRemoveExpiredItems(CNSocket* sock, Player* player) {
|
||||
int32_t currentTime = getTimestamp();
|
||||
|
||||
/* prepare packet
|
||||
* yes, this is a varadic packet, however analyzing client behavior and code
|
||||
* it only checks takes the first item sent into account
|
||||
* yes, this is very stupid
|
||||
* therefore, we delete all but 1 expired vehicle while loading player
|
||||
* to delete the last one here so player gets a notification
|
||||
*/
|
||||
// if there are expired items in bank just remove them silently
|
||||
if (settings::REMOVEEXPIREDITEMSFROMBANK) {
|
||||
for (int i = 0; i < ABANK_COUNT; i++) {
|
||||
if (player->Bank[i].iTimeLimit < currentTime && player->Bank[i].iTimeLimit != 0) {
|
||||
memset(&player->Bank[i], 0, sizeof(sItemBase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL);
|
||||
// collect items to remove and data for the packet
|
||||
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);
|
||||
// we know it's only one trailing struct, so we can skip full validation
|
||||
uint8_t respbuf[resplen]; // not a variable length array, don't worry
|
||||
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);
|
||||
uint8_t respbuf[CN_PACKET_BODY_SIZE];
|
||||
memset(respbuf, 0, CN_PACKET_BODY_SIZE);
|
||||
|
||||
auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf;
|
||||
|
||||
for (size_t i = 0; i < itemData.size(); i++) {
|
||||
auto itemToDeletePtr = (sTimeLimitItemDeleteInfo2CL*)(
|
||||
respbuf + sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL) * i
|
||||
);
|
||||
itemToDeletePtr->eIL = itemData[i].eIL;
|
||||
itemToDeletePtr->iSlotNum = itemData[i].iSlotNum;
|
||||
packet->iItemListCount++;
|
||||
}
|
||||
|
||||
packet->iItemListCount = 1;
|
||||
itemData->eIL = player->toRemoveVehicle.eIL;
|
||||
itemData->iSlotNum = player->toRemoveVehicle.iSlotNum;
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_PC_DELETE_TIME_LIMIT_ITEM, resplen);
|
||||
|
||||
// delete serverside
|
||||
if (player->toRemoveVehicle.eIL == 0)
|
||||
memset(&player->Equip[8], 0, sizeof(sItemBase));
|
||||
else
|
||||
memset(&player->Inven[player->toRemoveVehicle.iSlotNum], 0, sizeof(sItemBase));
|
||||
// delete items serverside and send unequip packets
|
||||
for (size_t i = 0; i < itemData.size(); i++) {
|
||||
sItemBase* item = toRemove[i];
|
||||
memset(item, 0, sizeof(sItemBase));
|
||||
|
||||
player->toRemoveVehicle.eIL = 0;
|
||||
player->toRemoveVehicle.iSlotNum = 0;
|
||||
// send item delete success packet
|
||||
// required for pre-academy builds
|
||||
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) {
|
||||
@@ -711,7 +882,69 @@ static void getMobDrop(sItemBase* reward, const std::vector<int>& weights, const
|
||||
reward->iID = crateIds[chosenIndex];
|
||||
}
|
||||
|
||||
static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRoll& rolled) {
|
||||
static int32_t calculateTaroReward(Player* plr, int baseAmount, int groupSize) {
|
||||
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);
|
||||
|
||||
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
|
||||
@@ -763,43 +996,20 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
|
||||
MiscDropType& miscDropType = Items::MiscDropTypes[drop.miscDropTypeId];
|
||||
|
||||
if (rolled.taros % miscDropChance.taroDropChanceTotal < miscDropChance.taroDropChance) {
|
||||
plr->money += miscDropType.taroAmount;
|
||||
// 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;
|
||||
}
|
||||
int32_t taros = calculateTaroReward(plr, miscDropType.taroAmount, groupSize);
|
||||
plr->addCapped(CappedValueType::TAROS, taros);
|
||||
}
|
||||
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 fm = miscDropType.fmAmount;
|
||||
if (levelDifference > 0)
|
||||
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);
|
||||
int32_t fm = calculateFMReward(plr, miscDropType.fmAmount, levelDifference, groupSize);
|
||||
plr->addCapped(CappedValueType::FUSIONMATTER, fm);
|
||||
Missions::updateFusionMatter(sock);
|
||||
}
|
||||
|
||||
if (rolled.potions % miscDropChance.potionDropChanceTotal < miscDropChance.potionDropChance)
|
||||
plr->batteryN += miscDropType.potionAmount;
|
||||
plr->addCapped(CappedValueType::BATTERY_N, miscDropType.potionAmount);
|
||||
if (rolled.boosts % miscDropChance.boostDropChanceTotal < miscDropChance.boostDropChance)
|
||||
plr->batteryW += miscDropType.boostAmount;
|
||||
|
||||
// caps
|
||||
if (plr->batteryW > 9999)
|
||||
plr->batteryW = 9999;
|
||||
if (plr->batteryN > 9999)
|
||||
plr->batteryN = 9999;
|
||||
plr->addCapped(CappedValueType::BATTERY_W, miscDropType.boostAmount);
|
||||
|
||||
// simple rewards
|
||||
reward->m_iCandy = plr->money;
|
||||
@@ -830,7 +1040,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) {
|
||||
void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const DropRoll& eventRolled, int groupSize) {
|
||||
// sanity check
|
||||
if (Items::MobToDropMap.find(mob->type) == Items::MobToDropMap.end()) {
|
||||
std::cout << "[WARN] Mob ID " << mob->type << " has no drops assigned" << std::endl;
|
||||
@@ -839,7 +1049,7 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const
|
||||
// find mob drop id
|
||||
int mobDropId = Items::MobToDropMap[mob->type];
|
||||
|
||||
giveSingleDrop(sock, mob, mobDropId, rolled);
|
||||
giveSingleDrop(sock, mob, mobDropId, rolled, groupSize);
|
||||
|
||||
if (settings::EVENTMODE != 0) {
|
||||
// sanity check
|
||||
@@ -850,14 +1060,13 @@ void Items::giveMobDrop(CNSocket *sock, Mob* mob, const DropRoll& rolled, const
|
||||
// find mob drop id
|
||||
int eventMobDropId = Items::EventToDropMap[settings::EVENTMODE];
|
||||
|
||||
giveSingleDrop(sock, mob, eventMobDropId, eventRolled);
|
||||
giveSingleDrop(sock, mob, eventMobDropId, eventRolled, groupSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Items::init() {
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_MOVE, itemMoveHandler);
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ITEM_DELETE, itemDeleteHandler);
|
||||
// this one is for gumballs
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_ITEM_USE, itemUseHandler);
|
||||
// Bank
|
||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BANK_OPEN, itemBankOpenHandler);
|
||||
|
||||
@@ -113,11 +113,11 @@ namespace Items {
|
||||
void init();
|
||||
|
||||
// mob drops
|
||||
void giveMobDrop(CNSocket *sock, Mob *mob, const DropRoll& rolled, const DropRoll& eventRolled);
|
||||
void giveMobDrop(CNSocket *sock, Mob *mob, const DropRoll& rolled, const DropRoll& eventRolled, int groupSize);
|
||||
|
||||
int findFreeSlot(Player *plr);
|
||||
Item* getItemData(int32_t id, int32_t type);
|
||||
void checkItemExpire(CNSocket* sock, Player* player);
|
||||
size_t checkAndRemoveExpiredItems(CNSocket* sock, Player* player);
|
||||
void setItemStats(Player* plr);
|
||||
void updateEquips(CNSocket* sock, Player* plr);
|
||||
|
||||
|
||||
@@ -12,6 +12,20 @@ std::map<int32_t, Reward*> Missions::Rewards;
|
||||
std::map<int32_t, TaskData*> Missions::Tasks;
|
||||
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) {
|
||||
// sanity check missionID so we don't get exceptions
|
||||
if (missionId < 0 || missionId > 1023) {
|
||||
@@ -148,7 +162,7 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
plr->Inven[slots[i]] = { 999, 999, 999, 0 }; // temp item; overwritten later
|
||||
}
|
||||
|
||||
@@ -162,21 +176,12 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
|
||||
memset(respbuf, 0, CN_PACKET_BODY_SIZE);
|
||||
|
||||
// update player
|
||||
plr->money += reward->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 money = calculateTaroReward(plr, reward->money);
|
||||
plr->addCapped(CappedValueType::TAROS, money);
|
||||
|
||||
if (plr->hasBuff(ECSB_REWARD_BLOB)) { // nano boost for fm
|
||||
int boost = 0;
|
||||
if (Nanos::getNanoBoost(plr)) // for gumballs
|
||||
boost = 1;
|
||||
updateFusionMatter(sock, reward->fusionmatter * (30 + boost) / 25);
|
||||
} else
|
||||
updateFusionMatter(sock, reward->fusionmatter);
|
||||
int32_t fusionMatter = calculateFMReward(plr, reward->fusionmatter);
|
||||
plr->addCapped(CappedValueType::FUSIONMATTER, fusionMatter);
|
||||
Missions::updateFusionMatter(sock);
|
||||
|
||||
// simple rewards
|
||||
resp->m_iCandy = plr->money;
|
||||
@@ -511,17 +516,13 @@ 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));
|
||||
}
|
||||
|
||||
void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
void Missions::updateFusionMatter(CNSocket* sock) {
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
plr->fusionmatter += fusion;
|
||||
|
||||
// there's a much lower FM cap in the Future
|
||||
int fmCap = AvatarGrowth[plr->level]["m_iFMLimit"];
|
||||
if (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
|
||||
if (plr->level >= 36)
|
||||
@@ -551,7 +552,7 @@ void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
||||
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));
|
||||
#else
|
||||
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
|
||||
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"]);
|
||||
plr->level++;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_LEVEL_SUCC, response);
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Missions {
|
||||
bool startTask(Player* plr, int TaskID);
|
||||
|
||||
// checks if player doesn't have n/n quest items
|
||||
void updateFusionMatter(CNSocket* sock, int fusion);
|
||||
void updateFusionMatter(CNSocket* sock);
|
||||
|
||||
void mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls);
|
||||
|
||||
|
||||
@@ -808,18 +808,17 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
||||
Items::DropRoll rolled;
|
||||
Items::DropRoll eventRolled;
|
||||
std::map<int, int> qitemRolls;
|
||||
std::vector<EntityRef> playersInRange;
|
||||
std::vector<Player*> playerRefs;
|
||||
|
||||
if (plr->group == nullptr) {
|
||||
playerRefs.push_back(plr);
|
||||
Combat::genQItemRolls(playerRefs, qitemRolls);
|
||||
Items::giveMobDrop(src.sock, self, rolled, eventRolled);
|
||||
Items::giveMobDrop(src.sock, self, rolled, eventRolled, 1);
|
||||
Missions::mobKilled(src.sock, self->type, qitemRolls);
|
||||
}
|
||||
else {
|
||||
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++) {
|
||||
CNSocket* sockTo = players[i].sock;
|
||||
Player* otherPlr = PlayerManager::getPlayer(sockTo);
|
||||
@@ -829,7 +828,14 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
||||
if (dist > 5000)
|
||||
continue;
|
||||
|
||||
Items::giveMobDrop(sockTo, self, rolled, eventRolled);
|
||||
playersInRange.push_back(players[i]);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,10 @@ void Nanos::addNano(CNSocket* sock, int16_t nanoID, int16_t slot, bool spendfm)
|
||||
*/
|
||||
plr->level = level;
|
||||
|
||||
if (spendfm)
|
||||
Missions::updateFusionMatter(sock, -(int)Missions::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
|
||||
if (spendfm) {
|
||||
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level-1]["m_iReqBlob_NanoCreate"]);
|
||||
Missions::updateFusionMatter(sock);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Send to client
|
||||
@@ -143,7 +145,7 @@ static void setNanoSkill(CNSocket* sock, sP_CL2FE_REQ_NANO_TUNE* skill) {
|
||||
return;
|
||||
#endif
|
||||
|
||||
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"];
|
||||
plr->subtractCapped(CappedValueType::FUSIONMATTER, (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoTune"]);
|
||||
|
||||
int reqItemCount = NanoTunings[skill->iTuneID].reqItemCount;
|
||||
int reqItemID = NanoTunings[skill->iTuneID].reqItems;
|
||||
@@ -190,7 +192,7 @@ int Nanos::nanoStyle(int nanoID) {
|
||||
}
|
||||
|
||||
bool Nanos::getNanoBoost(Player* plr) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (plr->equippedNanos[i] == plr->activeNano)
|
||||
if (plr->hasBuff(ECSB_STIMPAKSLOT1 + i))
|
||||
return true;
|
||||
@@ -355,7 +357,7 @@ static void nanoPotionHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
sock->sendPacket(response, P_FE2CL_REP_CHARGE_NANO_STAMINA);
|
||||
// now update serverside
|
||||
player->batteryN -= difference;
|
||||
player->subtractCapped(CappedValueType::BATTERY_N, difference);
|
||||
player->Nanos[nano.iID].iStamina += difference;
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,14 @@ struct BuffStack;
|
||||
|
||||
#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 {
|
||||
int accountId = 0;
|
||||
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||
@@ -65,8 +73,6 @@ struct Player : public Entity, public ICombatant {
|
||||
sItemBase QInven[AQINVEN_COUNT] = {};
|
||||
int32_t CurrentMissionID = 0;
|
||||
|
||||
sTimeLimitItemDeleteInfo2CL toRemoveVehicle = {};
|
||||
|
||||
Group* group = nullptr;
|
||||
|
||||
bool notify = false;
|
||||
@@ -84,7 +90,14 @@ struct Player : public Entity, public ICombatant {
|
||||
time_t lastShot = 0;
|
||||
std::vector<sItemBase> buyback = {};
|
||||
|
||||
Player() { kind = EntityKind::PLAYER; }
|
||||
double rateF[5] = { 1.0, 1.0, 1.0, 1.0, 1.0 };
|
||||
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 disappearFromViewOf(CNSocket *sock) override;
|
||||
@@ -111,4 +124,11 @@ struct Player : public Entity, public ICombatant {
|
||||
|
||||
sNano* getActiveNano();
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -243,9 +243,19 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// client doesnt read this, it gets it from charinfo
|
||||
// response.PCLoadData2CL.PCStyle2 = plr->PCStyle2;
|
||||
// inventory
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++)
|
||||
|
||||
// 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
|
||||
for (int i = 0; i < AINVEN_COUNT; i++)
|
||||
response.PCLoadData2CL.aInven[i] = plr->Inven[i];
|
||||
// quest inventory
|
||||
@@ -384,7 +394,7 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
Chat::sendServerMessage(sock, settings::MOTDSTRING); // MOTD
|
||||
Missions::failInstancedMissions(sock); // auto-fail missions
|
||||
Buddies::sendBuddyList(sock); // buddy list
|
||||
Items::checkItemExpire(sock, plr); // vehicle expiration
|
||||
Items::checkAndRemoveExpiredItems(sock, plr); // vehicle and booster expiration
|
||||
|
||||
plr->initialLoadDone = true;
|
||||
}
|
||||
@@ -495,7 +505,6 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
||||
|
||||
if (plr->group != nullptr) {
|
||||
|
||||
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->getCompositeCondition();
|
||||
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
||||
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
||||
@@ -517,9 +526,7 @@ static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
if (plr->instanceID != 0)
|
||||
return;
|
||||
|
||||
bool expired = plr->Equip[8].iTimeLimit < getTimestamp() && plr->Equip[8].iTimeLimit != 0;
|
||||
|
||||
if (plr->Equip[8].iID > 0 && !expired) {
|
||||
if (plr->Equip[EQUIP_SLOT_VEHICLE].iID > 0) {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_SUCC, response);
|
||||
sock->sendPacket(response, P_FE2CL_PC_VEHICLE_ON_SUCC);
|
||||
|
||||
@@ -533,30 +540,6 @@ static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||
} else {
|
||||
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_FAIL, response);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +568,7 @@ static void changePlayerGuide(CNSocket *sock, CNPacketData *data) {
|
||||
}
|
||||
|
||||
// start Blossom nano mission if applicable
|
||||
Missions::updateFusionMatter(sock, 0);
|
||||
Missions::updateFusionMatter(sock);
|
||||
}
|
||||
// save it on player
|
||||
plr->mentor = pkt->iMentor;
|
||||
@@ -607,6 +590,23 @@ static void setFirstUseFlag(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
#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) {
|
||||
if (players.find(key) != players.end())
|
||||
return players[key];
|
||||
@@ -620,6 +620,10 @@ std::string PlayerManager::getPlayerName(Player *plr, bool id) {
|
||||
if (plr == nullptr)
|
||||
return "NOT IN GAME";
|
||||
|
||||
if (plr->PCStyle.iNameCheck != 1) {
|
||||
return "Player " + std::to_string(plr->iID);
|
||||
}
|
||||
|
||||
std::string ret = "";
|
||||
if (id && plr->accountLevel <= 30)
|
||||
ret += "(GM) ";
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace PlayerManager {
|
||||
extern std::map<CNSocket*, Player*> players;
|
||||
void init();
|
||||
|
||||
void exitPlayerVehicle(CNSocket* sock, CNPacketData* data);
|
||||
|
||||
void removePlayer(CNSocket* key);
|
||||
|
||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Items.hpp"
|
||||
#include "Nanos.hpp"
|
||||
|
||||
using namespace Racing;
|
||||
|
||||
@@ -14,6 +15,15 @@ std::map<int32_t, EPInfo> Racing::EPData;
|
||||
std::map<CNSocket*, EPRace> Racing::EPRaces;
|
||||
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) {
|
||||
auto req = (sP_CL2FE_REQ_EP_RACE_START*)data->buf;
|
||||
|
||||
@@ -155,7 +165,9 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
||||
resp.iEPRaceMode = EPRaces[sock].mode;
|
||||
resp.iEPRewardFM = fm;
|
||||
|
||||
Missions::updateFusionMatter(sock, resp.iEPRewardFM);
|
||||
int32_t fmReward = calculateFMReward(plr, resp.iEPRewardFM);
|
||||
plr->addCapped(CappedValueType::FUSIONMATTER, fmReward);
|
||||
Missions::updateFusionMatter(sock);
|
||||
|
||||
resp.iFusionMatter = plr->fusionmatter;
|
||||
resp.iFatigue = 50;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "TableData.hpp"
|
||||
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Items.hpp"
|
||||
@@ -79,6 +81,25 @@ static void loadXDT(json& xdtData) {
|
||||
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
|
||||
|
||||
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
|
||||
json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// these are added to the NPC's static key to avoid collisions
|
||||
const int NPC_ID_OFFSET = 1;
|
||||
const int MOB_ID_OFFSET = 10000;
|
||||
const int MOB_GROUP_ID_OFFSET = 20000;
|
||||
const int MOB_GROUP_ID_OFFSET = 30000;
|
||||
|
||||
// typedef for JSON object because I don't want to type nlohmann::json every time
|
||||
typedef nlohmann::json json;
|
||||
|
||||
@@ -230,7 +230,7 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
Player* plr2 = PlayerManager::getPlayer(otherSock);
|
||||
|
||||
|
||||
if (!(plr->isTrading && plr2->isTrading)) { // both players must be trading
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_TRADE_CONFIRM_ABORT, resp);
|
||||
resp.iID_Request = plr2->iID;
|
||||
@@ -273,14 +273,16 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
||||
resp2.iID_Request = pacdat->iID_Request;
|
||||
resp2.iID_From = pacdat->iID_From;
|
||||
resp2.iID_To = pacdat->iID_To;
|
||||
plr->money = plr->money + plr2->moneyInTrade - plr->moneyInTrade;
|
||||
plr->subtractCapped(CappedValueType::TAROS, plr->moneyInTrade);
|
||||
plr->addCapped(CappedValueType::TAROS, plr2->moneyInTrade);
|
||||
resp2.iCandy = plr->money;
|
||||
memcpy(resp2.Item, plr2->Trade, sizeof(plr2->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));
|
||||
|
||||
plr2->money = plr2->money + plr->moneyInTrade - plr2->moneyInTrade;
|
||||
plr2->subtractCapped(CappedValueType::TAROS, plr2->moneyInTrade);
|
||||
plr2->addCapped(CappedValueType::TAROS, plr->moneyInTrade);
|
||||
resp2.iCandy = plr2->money;
|
||||
memcpy(resp2.Item, plr->Trade, sizeof(plr->Trade));
|
||||
memcpy(resp2.ItemStay, plr2->Trade, sizeof(plr2->Trade));
|
||||
@@ -358,7 +360,7 @@ static void tradeRegisterItem(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// since you can spread items like gumballs over multiple slots, we need to count them all
|
||||
// to make sure the inventory shows the right value during trade.
|
||||
int count = 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (plr->Trade[i].iInvenNum == pacdat->Item.iInvenNum)
|
||||
count += plr->Trade[i].iOpt;
|
||||
@@ -410,7 +412,7 @@ static void tradeUnregisterItem(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
// since you can spread items like gumballs over multiple slots, we need to count them all
|
||||
// to make sure the inventory shows the right value during trade.
|
||||
int count = 0;
|
||||
int count = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (plr->Trade[i].iInvenNum == resp.InvenItem.iInvenNum)
|
||||
count += plr->Trade[i].iOpt;
|
||||
@@ -451,7 +453,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));
|
||||
otherSock->sendPacket((void*)&resp, P_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC, sizeof(sP_FE2CL_REP_PC_TRADE_CASH_REGISTER_SUCC));
|
||||
|
||||
plr->moneyInTrade = pacdat->iCandy;
|
||||
plr->setCapped(CappedValueType::TAROS_IN_TRADE, pacdat->iCandy);
|
||||
plr->isTradeConfirm = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
TransportRoute route = Routes[req->iTransporationID];
|
||||
plr->money -= route.cost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, route.cost);
|
||||
|
||||
TransportLocation* target = nullptr;
|
||||
switch (route.type) {
|
||||
@@ -143,7 +143,7 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
// refund and send alert packet
|
||||
plr->money += route.cost;
|
||||
plr->addCapped(CappedValueType::TAROS, route.cost);
|
||||
INITSTRUCT(sP_FE2CL_ANNOUNCE_MSG, alert);
|
||||
alert.iAnnounceType = 0; // don't think this lets us make a confirm dialog
|
||||
alert.iDuringTime = 3;
|
||||
|
||||
@@ -72,7 +72,7 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_ITEM_BUY_SUCC, resp);
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, itemCost);
|
||||
plr->Inven[slot] = req->Item;
|
||||
|
||||
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);
|
||||
|
||||
// increment taros
|
||||
plr->money += itemData->sellPrice * req->iItemCnt;
|
||||
plr->addCapped(CappedValueType::TAROS, itemData->sellPrice * req->iItemCnt);
|
||||
|
||||
// modify item
|
||||
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;
|
||||
}
|
||||
|
||||
plr->money = plr->money - itemCost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, itemCost);
|
||||
plr->Inven[slot] = item;
|
||||
|
||||
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);
|
||||
|
||||
int cost = req->Item.iOpt * 100;
|
||||
if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost || req->Item.iOpt < 0) { // sanity check
|
||||
if ((req->Item.iID == 3 ? (plr->batteryW >= PC_BATTERY_MAX) : (plr->batteryN >= PC_BATTERY_MAX)) || plr->money < cost || req->Item.iOpt < 0) { // sanity check
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp);
|
||||
failResp.iErrorCode = 0;
|
||||
sock->sendPacket(failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL);
|
||||
@@ -281,17 +281,11 @@ static void vendorBuyBattery(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
cost = plr->batteryW + plr->batteryN;
|
||||
plr->batteryW += req->Item.iID == 3 ? 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;
|
||||
plr->addCapped(CappedValueType::BATTERY_W, req->Item.iID == 3 ? req->Item.iOpt * 100 : 0);
|
||||
plr->addCapped(CappedValueType::BATTERY_N, req->Item.iID == 4 ? req->Item.iOpt * 100 : 0);
|
||||
|
||||
cost = plr->batteryW + plr->batteryN - cost;
|
||||
plr->money -= cost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, cost);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp);
|
||||
|
||||
@@ -364,7 +358,7 @@ static void vendorCombineItems(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
float rolled = Rand::randFloat(100.0f); // success chance out of 100
|
||||
//std::cout << rolled << " vs " << successChance << std::endl;
|
||||
plr->money -= cost;
|
||||
plr->subtractCapped(CappedValueType::TAROS, cost);
|
||||
|
||||
|
||||
INITSTRUCT(sP_FE2CL_REP_PC_ITEM_COMBINATION_SUCC, resp);
|
||||
|
||||
@@ -365,14 +365,8 @@ void CNServer::init() {
|
||||
}
|
||||
|
||||
// attach socket to the port
|
||||
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
|
||||
if (!setSocketOption(sock, SOL_SOCKET, SO_REUSEADDR, 1)) {
|
||||
std::cerr << "[FATAL] OpenFusion: setsockopt failed" << std::endl;
|
||||
printSocketError("setsockopt");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
address.sin_family = AF_INET;
|
||||
@@ -414,6 +408,18 @@ void CNServer::init() {
|
||||
CNServer::CNServer() {};
|
||||
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) {
|
||||
fds.push_back({s, POLLIN});
|
||||
}
|
||||
@@ -462,6 +468,9 @@ void CNServer::start() {
|
||||
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))
|
||||
continue;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
// posix platform
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
@@ -239,6 +240,7 @@ protected:
|
||||
|
||||
bool active = true;
|
||||
|
||||
bool setSocketOption(SOCKET s, int level, int option, int value);
|
||||
void addPollFD(SOCKET s);
|
||||
void removePollFD(int i);
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
// wrapper for U16toU8
|
||||
#define ARRLEN(x) (sizeof(x)/sizeof(*x))
|
||||
#define AUTOU8(x) std::string((char*)x, ARRLEN(x))
|
||||
#define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))
|
||||
#define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))
|
||||
|
||||
// TODO: rewrite U16toU8 & U8toU16 to not use codecvt
|
||||
|
||||
@@ -67,4 +67,7 @@ void terminate(int);
|
||||
#error Invalid PROTOCOL_VERSION
|
||||
#endif
|
||||
|
||||
#define AEQUIP_COUNT_MINUS_BOOSTERS 9
|
||||
#define AEQUIP_COUNT_WITH_BOOSTERS 12
|
||||
|
||||
sSYSTEMTIME timeStampToStruct(uint64_t time);
|
||||
|
||||
@@ -225,6 +225,15 @@ enum {
|
||||
SIZEOF_NANO_TUNE_NEED_ITEM_SLOT = 10,
|
||||
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_BUSY = 2,
|
||||
MSG_OFFLINE = 0,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define DATABASE_VERSION 5
|
||||
#define DATABASE_VERSION 6
|
||||
|
||||
namespace Database {
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Database {
|
||||
bool isNameFree(std::string firstName, std::string lastName);
|
||||
bool isSlotFree(int accountId, int slotNum);
|
||||
/// returns ID, 0 if something failed
|
||||
int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID);
|
||||
int createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck);
|
||||
/// returns true if query succeeded
|
||||
bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId);
|
||||
/// returns true if query succeeded
|
||||
@@ -85,7 +85,7 @@ namespace Database {
|
||||
};
|
||||
void evaluateCustomName(int characterID, CustomName decision);
|
||||
/// returns true if query succeeded
|
||||
bool changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId);
|
||||
bool changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck);
|
||||
|
||||
// getting players
|
||||
void getPlayer(Player* plr, int id);
|
||||
|
||||
@@ -196,18 +196,16 @@ void Database::updateEmailContent(EmailData* data) {
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
void Database::deleteEmailAttachments(int playerID, int index, int slot) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
static void _deleteEmailAttachments(int playerID, int index, int slot) {
|
||||
sqlite3_stmt* stmt;
|
||||
|
||||
std::string sql(R"(
|
||||
DELETE FROM EmailItems
|
||||
WHERE PlayerID = ? AND MsgIndex = ?;
|
||||
WHERE PlayerID = ? AND MsgIndex = ?
|
||||
)");
|
||||
|
||||
if (slot != -1)
|
||||
sql += " AND \"Slot\" = ? ";
|
||||
sql += " AND \"Slot\" = ?";
|
||||
sql += ";";
|
||||
|
||||
sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, NULL);
|
||||
@@ -221,6 +219,11 @@ void Database::deleteEmailAttachments(int playerID, int index, int slot) {
|
||||
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) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
@@ -234,12 +237,15 @@ void Database::deleteEmails(int playerID, int64_t* indices) {
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int64_t msgIndex = indices[i];
|
||||
sqlite3_bind_int(stmt, 1, playerID);
|
||||
sqlite3_bind_int64(stmt, 2, indices[i]);
|
||||
sqlite3_bind_int64(stmt, 2, msgIndex);
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to delete an email: " << sqlite3_errmsg(db) << std::endl;
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
// delete all attachments
|
||||
_deleteEmailAttachments(playerID, msgIndex, -1);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
@@ -323,12 +329,23 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Pl
|
||||
sqlite3_bind_int(stmt, 7, item.iTimeLimit);
|
||||
|
||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||
std::cout << "[WARN] Database: Failed to send email: " << sqlite3_errmsg(db) << std::endl;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
// 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;
|
||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sqlite3_reset(stmt);
|
||||
sqlite3_clear_bindings(stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "db/internal.hpp"
|
||||
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
|
||||
#include "bcrypt/BCrypt.hpp"
|
||||
|
||||
void Database::findAccount(Account* account, std::string login) {
|
||||
@@ -291,7 +293,7 @@ bool Database::isSlotFree(int accountId, int slotNum) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
|
||||
int Database::createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
@@ -304,22 +306,17 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
)";
|
||||
sqlite3_stmt* stmt;
|
||||
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
||||
std::string lastName = AUTOU16TOU8(save->szLastName);
|
||||
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_int(stmt, 1, AccountID);
|
||||
sqlite3_bind_int(stmt, 2, save->iSlotNum);
|
||||
sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL);
|
||||
sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL);
|
||||
sqlite3_bind_int(stmt, 1, accountId);
|
||||
sqlite3_bind_int(stmt, 2, slot);
|
||||
sqlite3_bind_text(stmt, 3, firstName, -1, NULL);
|
||||
sqlite3_bind_text(stmt, 4, lastName, -1, NULL);
|
||||
sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
|
||||
sqlite3_bind_int(stmt, 6, settings::SPAWN_Y);
|
||||
sqlite3_bind_int(stmt, 7, settings::SPAWN_Z);
|
||||
sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE);
|
||||
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);
|
||||
|
||||
// blobs
|
||||
@@ -648,7 +645,7 @@ void Database::evaluateCustomName(int characterID, CustomName decision) {
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
|
||||
bool Database::changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
const char* sql = R"(
|
||||
@@ -662,15 +659,10 @@ bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
|
||||
sqlite3_stmt* stmt;
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
|
||||
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
||||
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_text(stmt, 1, firstName, -1, NULL);
|
||||
sqlite3_bind_text(stmt, 2, lastName, -1, NULL);
|
||||
sqlite3_bind_int(stmt, 3, nameCheck);
|
||||
sqlite3_bind_int(stmt, 4, save->iPCUID);
|
||||
sqlite3_bind_int(stmt, 4, playerId);
|
||||
sqlite3_bind_int(stmt, 5, accountId);
|
||||
|
||||
int rc = sqlite3_step(stmt);
|
||||
|
||||
@@ -2,40 +2,6 @@
|
||||
|
||||
// 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) {
|
||||
std::lock_guard<std::mutex> lock(dbCrit);
|
||||
|
||||
@@ -93,13 +59,13 @@ void Database::getPlayer(Player* plr, int id) {
|
||||
plr->angle = sqlite3_column_int(stmt, 15);
|
||||
plr->HP = sqlite3_column_int(stmt, 16);
|
||||
plr->accountLevel = sqlite3_column_int(stmt, 17);
|
||||
plr->fusionmatter = sqlite3_column_int(stmt, 18);
|
||||
plr->money = sqlite3_column_int(stmt, 19);
|
||||
plr->setCapped(CappedValueType::FUSIONMATTER, sqlite3_column_int(stmt, 18));
|
||||
plr->setCapped(CappedValueType::TAROS, sqlite3_column_int(stmt, 19));
|
||||
|
||||
memcpy(plr->aQuestFlag, sqlite3_column_blob(stmt, 20), sizeof(plr->aQuestFlag));
|
||||
|
||||
plr->batteryW = sqlite3_column_int(stmt, 21);
|
||||
plr->batteryN = sqlite3_column_int(stmt, 22);
|
||||
plr->setCapped(CappedValueType::BATTERY_W, sqlite3_column_int(stmt, 21));
|
||||
plr->setCapped(CappedValueType::BATTERY_N, sqlite3_column_int(stmt, 22));
|
||||
plr->mentor = sqlite3_column_int(stmt, 23);
|
||||
plr->iWarpLocationFlag = sqlite3_column_int(stmt, 24);
|
||||
|
||||
@@ -160,8 +126,6 @@ void Database::getPlayer(Player* plr, int id) {
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
removeExpiredVehicles(plr);
|
||||
|
||||
// get quest inventory
|
||||
sql = R"(
|
||||
SELECT Slot, ID, Opt
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
|
||||
#include "core/CNShared.hpp"
|
||||
#include "db/Database.hpp"
|
||||
@@ -12,6 +13,12 @@
|
||||
|
||||
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) {
|
||||
serverType = "login";
|
||||
port = p;
|
||||
@@ -285,10 +292,29 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
|
||||
|
||||
int errorCode = 0;
|
||||
if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
|
||||
errorCode = 4;
|
||||
} else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
|
||||
errorCode = 1;
|
||||
|
||||
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;
|
||||
} else if (!Database::isNameFree(firstName, lastName)) {
|
||||
errorCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorCode != 0) {
|
||||
@@ -306,12 +332,19 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
||||
if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum))
|
||||
return invalidCharacter(sock);
|
||||
|
||||
resp.iPC_UID = Database::createCharacter(save, loginSessions[sock].userID);
|
||||
resp.iPC_UID = Database::createCharacter(save->iSlotNum, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck);
|
||||
// if query somehow failed
|
||||
if (resp.iPC_UID == 0) {
|
||||
std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl;
|
||||
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.iGender = save->iGender;
|
||||
|
||||
@@ -327,7 +360,9 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: new character created" << std::endl;
|
||||
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
|
||||
std::cout << "\tName: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
|
||||
std::cout << "\tName: " << firstName << " " << lastName;
|
||||
if (nameCheck != 1) std::cout << " (pending approval)";
|
||||
std::cout << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
@@ -494,11 +529,30 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
||||
auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
|
||||
|
||||
int errorCode = 0;
|
||||
if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
|
||||
errorCode = 4;
|
||||
|
||||
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;
|
||||
}
|
||||
else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
|
||||
errorCode = 1;
|
||||
|
||||
if (errorCode == 0) {
|
||||
if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) {
|
||||
errorCode = 4;
|
||||
}
|
||||
else if (!Database::isNameFree(firstName, lastName)) {
|
||||
errorCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorCode != 0) {
|
||||
@@ -513,9 +567,15 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Database::changeName(save, loginSessions[sock].userID))
|
||||
if (!Database::changeName(save->iPCUID, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck))
|
||||
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);
|
||||
resp.iPC_UID = save->iPCUID;
|
||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName));
|
||||
@@ -527,8 +587,10 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Name check success for character [" << save->iPCUID << "]" << std::endl;
|
||||
std::cout << "\tNew name: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
|
||||
std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl;
|
||||
std::cout << "\tNew name: " << firstName << " " << lastName;
|
||||
if (nameCheck != 1) std::cout << " (pending approval)";
|
||||
std::cout << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
@@ -621,6 +683,42 @@ bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tr
|
||||
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) {
|
||||
//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}(?! +))*$)");
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace LoginServer {
|
||||
extern std::vector<std::string> WheelFirstNames;
|
||||
extern std::vector<std::string> WheelMiddleNames;
|
||||
extern std::vector<std::string> WheelLastNames;
|
||||
}
|
||||
|
||||
struct CNLoginData {
|
||||
int userID;
|
||||
time_t lastHeartbeat;
|
||||
@@ -51,6 +57,7 @@ private:
|
||||
static bool isPasswordGood(std::string& password);
|
||||
static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword);
|
||||
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 isAuthMethodAllowed(AuthMethod authMethod);
|
||||
static bool checkUsername(CNSocket* sock, std::string& username);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "MobAI.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "TableData.hpp" // for flush()
|
||||
#include "Items.hpp" // for checkAndRemoveExpiredItems()
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@@ -23,6 +24,7 @@ CNShardServer::CNShardServer(uint16_t p) {
|
||||
pHandler = &CNShardServer::handlePacket;
|
||||
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
||||
REGISTER_SHARD_TIMER(periodicSaveTimer, settings::DBSAVEINTERVAL*1000);
|
||||
REGISTER_SHARD_TIMER(periodicItemExpireTimer, 60000);
|
||||
init();
|
||||
|
||||
if (settings::MONITORENABLED)
|
||||
@@ -88,6 +90,22 @@ void CNShardServer::periodicSaveTimer(CNServer* serv, time_t currTime) {
|
||||
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) {
|
||||
return Monitor::acceptConnection(fds[i].fd, fds[i].revents);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ private:
|
||||
|
||||
static void keepAliveTimer(CNServer*, time_t);
|
||||
static void periodicSaveTimer(CNServer* serv, time_t currTime);
|
||||
static void periodicItemExpireTimer(CNServer* serv, time_t currTime);
|
||||
|
||||
public:
|
||||
static std::map<uint32_t, PacketHandler> ShardPackets;
|
||||
|
||||
@@ -14,6 +14,13 @@ static std::mutex sockLock; // guards socket list
|
||||
static std::list<SOCKET> sockets;
|
||||
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) {
|
||||
int n = 0;
|
||||
int sock = *it;
|
||||
@@ -99,15 +106,23 @@ outer:
|
||||
}
|
||||
|
||||
// chat
|
||||
for (auto& str : Chat::dump) {
|
||||
for (auto& str : chats) {
|
||||
n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str());
|
||||
|
||||
if (!transmit(it, buff, n))
|
||||
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
|
||||
for (auto& str : Email::dump) {
|
||||
for (auto& str : emails) {
|
||||
n = process_email(buff, str);
|
||||
|
||||
if (!transmit(it, buff, n))
|
||||
@@ -117,14 +132,24 @@ 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))
|
||||
continue;
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
Chat::dump.clear();
|
||||
Email::dump.clear();
|
||||
chats.clear();
|
||||
bcasts.clear();
|
||||
emails.clear();
|
||||
namereqs.clear();
|
||||
}
|
||||
|
||||
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
#include "core/Core.hpp"
|
||||
|
||||
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();
|
||||
bool acceptConnection(SOCKET, uint16_t);
|
||||
};
|
||||
|
||||
@@ -12,7 +12,8 @@ bool settings::SANDBOX = true;
|
||||
std::string settings::SANDBOXEXTRAPATH = "";
|
||||
|
||||
int settings::LOGINPORT = 23000;
|
||||
bool settings::APPROVEALLNAMES = true;
|
||||
bool settings::APPROVEWHEELNAMES = true;
|
||||
bool settings::APPROVECUSTOMNAMES = true;
|
||||
bool settings::AUTOCREATEACCOUNTS = true;
|
||||
std::string settings::AUTHMETHODS = "password";
|
||||
int settings::DBSAVEINTERVAL = 240;
|
||||
@@ -73,6 +74,19 @@ int settings::EVENTMODE = 0;
|
||||
// race settings
|
||||
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() {
|
||||
INIReader reader("config.ini");
|
||||
|
||||
@@ -89,7 +103,8 @@ void settings::init() {
|
||||
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
|
||||
SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH);
|
||||
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
|
||||
APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES);
|
||||
APPROVEWHEELNAMES = reader.GetBoolean("login", "acceptallwheelnames", APPROVEWHEELNAMES);
|
||||
APPROVECUSTOMNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVECUSTOMNAMES);
|
||||
AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS);
|
||||
AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS);
|
||||
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
|
||||
@@ -115,13 +130,30 @@ void settings::init() {
|
||||
TDATADIR = reader.Get("shard", "tdatadir", TDATADIR);
|
||||
PATCHDIR = reader.Get("shard", "patchdir", PATCHDIR);
|
||||
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);
|
||||
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
||||
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
|
||||
ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
|
||||
IZRACESCORECAPPED = reader.GetBoolean("shard", "izracescorecapped", IZRACESCORECAPPED);
|
||||
REMOVEEXPIREDITEMSFROMBANK = reader.GetBoolean("shard", "removeexpireditemsfrombank", REMOVEEXPIREDITEMSFROMBANK);
|
||||
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
|
||||
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
|
||||
MONITORLISTENIP = reader.Get("monitor", "listenip", MONITORLISTENIP);
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace settings {
|
||||
extern bool SANDBOX;
|
||||
extern std::string SANDBOXEXTRAPATH;
|
||||
extern int LOGINPORT;
|
||||
extern bool APPROVEALLNAMES;
|
||||
extern bool APPROVEWHEELNAMES;
|
||||
extern bool APPROVECUSTOMNAMES;
|
||||
extern bool AUTOCREATEACCOUNTS;
|
||||
extern std::string AUTHMETHODS;
|
||||
extern int DBSAVEINTERVAL;
|
||||
@@ -44,6 +45,11 @@ namespace settings {
|
||||
extern int MONITORINTERVAL;
|
||||
extern bool DISABLEFIRSTUSEFLAG;
|
||||
extern bool IZRACESCORECAPPED;
|
||||
extern bool DROPFIXESENABLED;
|
||||
extern bool LESSTAROFMINGROUPDISABLED;
|
||||
extern int TARORATE;
|
||||
extern int FUSIONMATTERRATE;
|
||||
extern bool REMOVEEXPIREDITEMSFROMBANK;
|
||||
|
||||
void init();
|
||||
}
|
||||
|
||||
2
tdata
2
tdata
Submodule tdata updated: bdb611b092...fed031b972
52
win-setup.ps1
Normal file
52
win-setup.ps1
Normal file
@@ -0,0 +1,52 @@
|
||||
# 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"
|
||||
Reference in New Issue
Block a user