2 Commits

Author SHA1 Message Date
3cfecd9644 Refuse to run if the specified build name isn't in the patch map 2023-06-26 06:48:12 +02:00
6537e38987 Replace enabledpatches config option with patchmap.json
This should make it a lot easier to manage patch directories when we add
support for each known client build.
2023-06-26 05:42:57 +02:00
13 changed files with 84 additions and 167 deletions

View File

@@ -1 +0,0 @@
version.h

26
.vscode/launch.json vendored
View File

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

View File

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

View File

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

View File

@@ -1,3 +1,8 @@
# name of the client build the server is targetting.
# used for determining which patches to apply.
# default is beta-20111013 for Academy, beta-20100104 otherwise.
#buildname=beta-20100104
# verbosity level
# 0 = mostly silence
# 1 = debug prints and unknown packets
@@ -46,11 +51,6 @@ motd=Welcome to OpenFusion!
# location of the patch folder
#patchdir=tdata/patch/
# Space-separated list of patch folders in patchdir to load from.
# If you uncomment this, note that Academy builds *must* contain 1013,
# and pre-Academy builds must *not* contain it.
#enabledpatches=1013
# xdt json filename
#xdtdata=xdt.json
# NPC json filename
@@ -61,6 +61,8 @@ motd=Welcome to OpenFusion!
#pathdata=paths.json
# drop json filename
#dropdata=drops.json
# patchmap json filename
#patchmapdata=patchmap.json
# gruntwork output filename (this is what you submit)
#gruntwork=gruntwork.json
# location of the database

View File

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

View File

@@ -69,41 +69,31 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
// Handle serverside value-changes
switch (setData->iSetValueType) {
case CN_GM_SET_VALUE_TYPE__HP:
response.iSetValue = plr->HP = setData->iSetValue;
case 1:
plr->HP = setData->iSetValue;
break;
case CN_GM_SET_VALUE_TYPE__WEAPON_BATTERY :
case 2:
plr->batteryW = setData->iSetValue;
// caps
if (plr->batteryW > 9999)
plr->batteryW = 9999;
response.iSetValue = plr->batteryW;
break;
case CN_GM_SET_VALUE_TYPE__NANO_BATTERY:
case 3:
plr->batteryN = setData->iSetValue;
// caps
if (plr->batteryN > 9999)
plr->batteryN = 9999;
response.iSetValue = plr->batteryN;
break;
case CN_GM_SET_VALUE_TYPE__FUSION_MATTER:
case 4:
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
response.iSetValue = plr->fusionmatter;
break;
case CN_GM_SET_VALUE_TYPE__CANDY:
response.iSetValue = plr->money = setData->iSetValue;
break;
case CN_GM_SET_VALUE_TYPE__SPEED:
case CN_GM_SET_VALUE_TYPE__JUMP:
response.iSetValue = setData->iSetValue;
case 5:
plr->money = setData->iSetValue;
break;
}
response.iPC_ID = setData->iPC_ID;
response.iSetValue = setData->iSetValue;
response.iSetValueType = setData->iSetValueType;
sock->sendPacket(response, P_FE2CL_GM_REP_PC_SET_VALUE);

View File

@@ -358,19 +358,23 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
int angle = (plr->angle + 180) % 360;
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
bool isGruntworkNpc = true;
// if it's a gruntwork NPC, rotate in-place
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) {
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
// add a rotation entry to the gruntwork file, unless it's already a gruntwork NPC
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()) {
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
+ std::to_string(npc->appearanceData.iNPC_ID));
} else {
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
isGruntworkNpc = false;
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
+ std::to_string(npc->appearanceData.iNPC_ID));
}
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) +
" for " + (isGruntworkNpc ? "gruntwork " : "") + "NPC " + std::to_string(npc->appearanceData.iNPC_ID));
// update rotation clientside by refreshing the player's chunks (same as the /refresh command)
PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID);
// update rotation clientside
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
pkt.NPCAppearanceData = npc->appearanceData;
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
}
static void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {

View File

@@ -97,47 +97,31 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
return; // IZ not found
EPInfo& epInfo = EPData[mapNum];
EPRace& epRace = EPRaces[sock];
// if there are no divide-by-zero dangers, and at least one factor has been specified
// we switch over to OG scoring
bool useOGScoring = (epInfo.maxPods > 0) && (epInfo.maxTime > 0) && (
(epInfo.scaleFactor > 0.0) || (epInfo.podFactor > 0.0) || (epInfo.timeFactor > 0.0));
uint64_t now = getTime() / 1000;
int timeDiff = now - epRace.startTime;
int podsCollected = epRace.collectedRings.size();
int score = 0, fm = 0;
if (useOGScoring) {
score = std::min(epInfo.maxScore, (int)std::exp(
(epInfo.podFactor * podsCollected) / epInfo.maxPods
- (epInfo.timeFactor * timeDiff) / epInfo.maxTime
+ epInfo.scaleFactor));
fm = (1.0 + std::exp(epInfo.scaleFactor - 1.0) * epInfo.podFactor * podsCollected) / epInfo.maxPods;
} else {
score = std::max(0, 500 * podsCollected - 10 * timeDiff);
fm = score * plr->level * (1.0f / 36) * 0.3f;
}
int timeDiff = now - EPRaces[sock].startTime;
int score = 500 * EPRaces[sock].collectedRings.size() - 10 * timeDiff;
if (score < 0) score = 0; // lol
int fm = score * plr->level * (1.0f / 36) * 0.3f;
// we submit the ranking first...
Database::RaceRanking postRanking = {};
postRanking.EPID = epInfo.EPID;
postRanking.EPID = EPData[mapNum].EPID;
postRanking.PlayerID = plr->iID;
postRanking.RingCount = podsCollected;
postRanking.RingCount = EPRaces[sock].collectedRings.size();
postRanking.Score = score;
postRanking.Time = timeDiff;
postRanking.Timestamp = getTimestamp();
Database::postRaceRanking(postRanking);
// ...then we get the top ranking, which may or may not be what we just submitted
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(epInfo.EPID, plr->iID);
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(EPData[mapNum].EPID, plr->iID);
INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
// get rank scores and rewards
std::vector<int>* rankScores = &EPRewards[epInfo.EPID].first;
std::vector<int>* rankRewards = &EPRewards[epInfo.EPID].second;
std::vector<int>* rankScores = &EPRewards[EPData[mapNum].EPID].first;
std::vector<int>* rankRewards = &EPRewards[EPData[mapNum].EPID].second;
// top ranking
int topRank = 0;

View File

@@ -5,11 +5,7 @@
#include "servers/CNShardServer.hpp"
struct EPInfo {
// available through XDT (maxScore may be updated by drops)
int zoneX, zoneY, EPID, maxScore;
// (maybe) available through drops
int maxTime = 0, maxPods = 0;
double scaleFactor = 0.0, podFactor = 0.0, timeFactor = 0.0;
int zoneX, zoneY, EPID, maxScore, maxTime;
};
struct EPRace {

View File

@@ -572,35 +572,8 @@ static void loadDrops(json& dropData) {
continue;
}
EPInfo& epInfo = Racing::EPData[EPMap];
// time limit isn't stored in the XDT, so we include it in the reward table instead
epInfo.maxTime = (int)race["TimeLimit"];
// update max score (if present)
if (race.find("ScoreCap") != race.end()) {
epInfo.maxScore = (int)race["ScoreCap"];
}
// update max pods (if present)
if (race.find("TotalPods") != race.end()) {
epInfo.maxPods = (int)race["TotalPods"];
}
// update scale factor (if present)
if (race.find("ScaleFactor") != race.end()) {
epInfo.scaleFactor = (double)race["ScaleFactor"];
}
// update pod factor (if present)
if (race.find("PodFactor") != race.end()) {
epInfo.podFactor = (double)race["PodFactor"];
}
// update time factor (if present)
if (race.find("TimeFactor") != race.end()) {
epInfo.timeFactor = (double)race["TimeFactor"];
}
Racing::EPData[EPMap].maxTime = race["TimeLimit"];
// score cutoffs
std::vector<int> rankScores;
@@ -1113,6 +1086,33 @@ static void patchJSON(json* base, json* patch) {
void TableData::init() {
int32_t nextId = INT32_MAX; // next dynamic ID to hand out
json patchmap;
// load patch map
{
std::fstream fstream;
fstream.open(settings::TDATADIR + "/" + settings::PATCHMAPJSON);
if (fstream.fail()) {
std::cerr << "[FATAL] Critical tdata file missing: " << settings::PATCHMAPJSON << std::endl;
exit(1);
}
if (fstream.peek() == std::ifstream::traits_type::eof()) {
std::cerr << "[FATAL] Critical tdata file is empty: " << settings::PATCHMAPJSON << std::endl;
exit(1);
}
fstream >> patchmap;
fstream.close();
}
// ensure that there is a patch list for the current build
if (patchmap["patchmap"].find(settings::BUILDNAME) == patchmap["patchmap"].end()) {
std::cerr << "[FATAL] Build name " << settings::BUILDNAME << " not found in " <<
settings::PATCHMAPJSON << std::endl;
exit(1);
}
// base JSON tables
json xdt, paths, drops, eggs, npcs, mobs, gruntwork;
@@ -1161,15 +1161,13 @@ void TableData::init() {
fstream >> *table.first;
}
// patching: load each patch directory specified in the config file
// split config field into individual patch entries
std::stringstream ss(settings::ENABLEDPATCHES);
std::istream_iterator<std::string> begin(ss);
std::istream_iterator<std::string> end;
// patching: load each patch directory specified in patchmap.json
// fetch list of patches that need to be applied for the current build
json patch;
for (auto it = begin; it != end; it++) {
json patchlist = patchmap["patchmap"][settings::BUILDNAME];
for (auto it = patchlist.begin(); it != patchlist.end(); it++) {
// this is the theoretical path of a corresponding patch for this file
std::string patchModuleName = *it;
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;

View File

@@ -47,12 +47,13 @@ std::string settings::GRUNTWORKJSON = "gruntwork.json";
std::string settings::MOTDSTRING = "Welcome to OpenFusion!";
std::string settings::DROPSJSON = "drops.json";
std::string settings::PATHJSON = "paths.json";
std::string settings::PATCHMAPJSON = "patchmap.json";
#ifdef ACADEMY
std::string settings::XDTJSON = "xdt1013.json";
std::string settings::ENABLEDPATCHES = "1013";
std::string settings::BUILDNAME = "beta-20111013";
#else
std::string settings::XDTJSON = "xdt.json";
std::string settings::ENABLEDPATCHES = "";
std::string settings::BUILDNAME = "beta-20100104";
#endif // ACADEMY
int settings::ACCLEVEL = 1;
@@ -78,6 +79,7 @@ void settings::init() {
return;
}
BUILDNAME = reader.Get("", "buildname", BUILDNAME);
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
@@ -105,7 +107,7 @@ void settings::init() {
DBPATH = reader.Get("shard", "dbpath", DBPATH);
TDATADIR = reader.Get("shard", "tdatadir", TDATADIR);
PATCHDIR = reader.Get("shard", "patchdir", PATCHDIR);
ENABLEDPATCHES = reader.Get("shard", "enabledpatches", ENABLEDPATCHES);
PATCHMAPJSON = reader.Get("shard", "patchmapdata", PATCHMAPJSON);
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);

View File

@@ -29,7 +29,8 @@ namespace settings {
extern std::string GRUNTWORKJSON;
extern std::string DBPATH;
extern std::string PATCHDIR;
extern std::string ENABLEDPATCHES;
extern std::string PATCHMAPJSON;
extern std::string BUILDNAME;
extern std::string TDATADIR;
extern int EVENTMODE;
extern bool MONITORENABLED;