Compare commits

..

No commits in common. "cd265af8e021eab101e735ea702ea3a0c644a821" and "4592fc42af9b9942d860c8e24a704e4ea50268ec" have entirely different histories.

7 changed files with 47 additions and 53 deletions

View File

@ -28,7 +28,8 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux). Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux).
### Hosting a server ### Hosting a server
1. Grab `OpenFusionServer-1.5-Original.zip` or `OpenFusionServer-1.5-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.5).
1. Grab `OpenFusionServer-1.5-original.zip` or `OpenFusionServer-1.5-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.5).
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server. 2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
3. Add a new server to the client's list: 3. Add a new server to the client's list:
1. For Description, enter anything you want. This is what will show up in the server list. 1. For Description, enter anything you want. This is what will show up in the server list.
@ -53,7 +54,10 @@ FusionFall consists of the following components:
The original game made use of the player's actual web browser to launch the game, but since then the NPAPI plugin interface the game relied on has been deprecated and is no longer available in most modern browsers. Both Retro and OpenFusion get around this issue by distributing an older version of Electron, a software package that is essentially a specialized web browser. The original game made use of the player's actual web browser to launch the game, but since then the NPAPI plugin interface the game relied on has been deprecated and is no longer available in most modern browsers. Both Retro and OpenFusion get around this issue by distributing an older version of Electron, a software package that is essentially a specialized web browser.
The browser/Electron client opens a web page with an `<embed>` tag of the appropriate MIME type, where the `src` param is the address of the game's `.unity3d` entrypoint. This triggers the browser to load an NPAPI plugin that handles said MIME type, in this case the Unity Web Player. The browser/Electron client opens a web page with an `<embed>` tag of MIME type `application/vnd.unity`, where the `src` param is the address of the game's `.unity3d` entrypoint.
This triggers the browser to load an NPAPI plugin that handles this MIME type, the Unity Web Player, which the browser looks for in `C:\Users\%USERNAME%\AppData\LocalLow\Unity\WebPlayer`.
The Web Player was previously copied there by `installUnity.bat`.
Note that the version of the web player distributed with OpenFusion expects a standard `UnityWeb` magic number for all assets, instead of Retro's modified `streamed` magic number. Note that the version of the web player distributed with OpenFusion expects a standard `UnityWeb` magic number for all assets, instead of Retro's modified `streamed` magic number.
This will potentially become relevant later, as people start experimenting and mixing and matching versions. This will potentially become relevant later, as people start experimenting and mixing and matching versions.
@ -62,7 +66,7 @@ The web player will execute the game code, which will request the following file
`/assetInfo.php` contains the address from which to fetch the rest of the game's assets (the "dongresources"). `/assetInfo.php` contains the address from which to fetch the rest of the game's assets (the "dongresources").
Normally those would be hosted on the same web server as the gateway, but the OpenFusion distribution (in it's default configuration) doesn't use a web server at all! Normally those would be hosted on the same web server as the gateway, but the OpenFusion distribution (in it's default configuration) doesn't use a web server at all!
It instead loads the web pages locally using the `file://` schema, and fetches the game's assets from a standard web server. It loads the web pages locally using the `file://` schema, and fetches the game's assets from Turner's CDN (which is still hosting them to this day!).
`/loginInfo.php` contains the IP:port pair of the FusionFall login server, which the client will connect to. This login server drives the client while it's in the Character Selection menu, as well as Character Creation and the Tutorial. `/loginInfo.php` contains the IP:port pair of the FusionFall login server, which the client will connect to. This login server drives the client while it's in the Character Selection menu, as well as Character Creation and the Tutorial.
@ -98,13 +102,26 @@ If you'd like to contribute to this project, please read [CONTRIBUTING.md](CONTR
## Gameplay ## Gameplay
The goal of the project is to faithfully recreate the game as it was at the time of the targeted build. The goal of the project is to faithfully recreate the game as it was at the time of the targeted build.
While most features are implemented and the game is playable start to finish, there may be missing functionality or bugs present. The server is not yet complete, however, and some functionality is still missing.
Depending on the server configuration, you'll have access to certain commands. Because the server is still in development, ordinary players are allowed access to a few admin commands:
For the public servers: Original has item spawning, the ability to set player speed/jump height, and teleportation enabled (default account level 50). ![](res/sane_upsell.png)
Meanwhile the Academy server is more meant for legitimate playthroughs (default account level 99).
When hosting a local server, you will have access to all commands by default (account level 1). ### Movement commands
* A `/speed` of around 2400 or 3000 is nice.
* A `/jump` of about 50 will send you soaring
* [This map](res/dong_number_map.png) (credit to Danny O) is useful for `/warp` coordinates.
* `/goto` is useful for more precise teleportation (ie. for getting into Infected Zones, etc.).
For a list of available commands, see [this wiki page](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list). ### Item commands
* `/itemN [type] [itemId] [amount]`
(Refer to the [item list](https://docs.google.com/spreadsheets/d/1mpoJ9iTHl_xLI4wQ_9UvIDYNcsDYscdkyaGizs43TCg/))
### Nano commands
* `/nano [id] (1-36)`
* `/nano_equip [id] (1-36) [slot] (0-2)`
* `/nano_unequip [slot] (0-2)`
* `/nano_active [slot] (0-2)`
### A full list of commands can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list).

View File

@ -365,7 +365,7 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
return ret; return ret;
} }
static bool checkRapidFire(CNSocket *sock, int targetCount, bool allowManyTargets) { static bool checkRapidFire(CNSocket *sock, int targetCount) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
time_t currTime = getTime(); time_t currTime = getTime();
@ -377,7 +377,7 @@ static bool checkRapidFire(CNSocket *sock, int targetCount, bool allowManyTarget
plr->lastShot = currTime; plr->lastShot = currTime;
// 3+ targets should never be possible // 3+ targets should never be possible
if (!allowManyTargets && targetCount > 3) if (targetCount > 3)
plr->suspicionRating += 10001; plr->suspicionRating += 10001;
// kill the socket when the player is too suspicious // kill the socket when the player is too suspicious
@ -396,7 +396,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
auto targets = (int32_t*)data->trailers; auto targets = (int32_t*)data->trailers;
// kick the player if firing too rapidly // kick the player if firing too rapidly
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt, false)) if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt))
return; return;
/* /*
@ -837,10 +837,6 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
return; return;
} }
// kick the player if firing too rapidly
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iTargetCnt, true))
return;
/* /*
* initialize response struct * initialize response struct
* rocket style hit doesn't work properly, so we're always sending this one * rocket style hit doesn't work properly, so we're always sending this one

View File

@ -66,7 +66,7 @@ static void racingCancel(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_REP_EP_RACE_CANCEL_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_EP_RACE_CANCEL_SUCC, resp);
sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_CANCEL_SUCC); sock->sendPacket(resp, P_FE2CL_REP_EP_RACE_CANCEL_SUCC);
/* /*
* This request packet is used for both cancelling the race via the * This request packet is used for both cancelling the race via the
* NPC at the start, *and* failing the race by running out of time. * NPC at the start, *and* failing the race by running out of time.
* If the latter is to happen, the client disables movement until it * If the latter is to happen, the client disables movement until it
@ -99,37 +99,31 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0) if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
return; // IZ not found return; // IZ not found
EPInfo& epInfo = EPData[mapNum];
EPRace& epRace = EPRaces[sock];
uint64_t now = getTime() / 1000; uint64_t now = getTime() / 1000;
int timeDiff = now - epRace.startTime;
int podsCollected = epRace.collectedRings.size();
int score = std::min(epInfo.maxScore, (int)std::exp( int timeDiff = now - EPRaces[sock].startTime;
(epInfo.podFactor * podsCollected) / epInfo.maxPods int score = 500 * EPRaces[sock].collectedRings.size() - 10 * timeDiff;
- (epInfo.timeFactor * timeDiff) / epInfo.maxTime if (score < 0) score = 0; // lol
+ epInfo.scaleFactor)); int fm = score * plr->level * (1.0f / 36) * 0.3f;
int fm = (1.0 + std::exp(epInfo.scaleFactor - 1.0) * epInfo.podFactor * podsCollected) / epInfo.maxPods;
// we submit the ranking first... // we submit the ranking first...
Database::RaceRanking postRanking = {}; Database::RaceRanking postRanking = {};
postRanking.EPID = epInfo.EPID; postRanking.EPID = EPData[mapNum].EPID;
postRanking.PlayerID = plr->iID; postRanking.PlayerID = plr->iID;
postRanking.RingCount = podsCollected; postRanking.RingCount = EPRaces[sock].collectedRings.size();
postRanking.Score = score; postRanking.Score = score;
postRanking.Time = timeDiff; postRanking.Time = timeDiff;
postRanking.Timestamp = getTimestamp(); postRanking.Timestamp = getTimestamp();
Database::postRaceRanking(postRanking); Database::postRaceRanking(postRanking);
// ...then we get the top ranking, which may or may not be what we just submitted // ...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); INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
// get rank scores and rewards // get rank scores and rewards
std::vector<int>* rankScores = &EPRewards[epInfo.EPID].first; std::vector<int>* rankScores = &EPRewards[EPData[mapNum].EPID].first;
std::vector<int>* rankRewards = &EPRewards[epInfo.EPID].second; std::vector<int>* rankRewards = &EPRewards[EPData[mapNum].EPID].second;
// top ranking // top ranking
int topRank = 0; int topRank = 0;

View File

@ -7,11 +7,7 @@
#include <set> #include <set>
struct EPInfo { struct EPInfo {
// available through XDT (maxScore may be updated by drops) int zoneX, zoneY, EPID, maxScore, maxTime;
int zoneX, zoneY, EPID, maxScore;
// available through drops
int maxTime, maxPods;
double scaleFactor, podFactor, timeFactor;
}; };
struct EPRace { struct EPRace {

View File

@ -375,7 +375,7 @@ static void loadPaths(json& pathData, int32_t* nextId) {
Transport::NPCPaths.push_back(pathTemplate); Transport::NPCPaths.push_back(pathTemplate);
} }
std::cout << "[INFO] Loaded " << Transport::NPCPaths.size() << " NPC paths" << std::endl; std::cout << "[INFO] Loaded " << Transport::NPCPaths.size() << " NPC paths" << std::endl;
} }
catch (const std::exception& err) { catch (const std::exception& err) {
std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl; std::cerr << "[FATAL] Malformed paths.json file! Reason:" << err.what() << std::endl;
@ -584,17 +584,8 @@ static void loadDrops(json& dropData) {
continue; continue;
} }
EPInfo& epInfo = Racing::EPData[EPMap]; // time limit isn't stored in the XDT, so we include it in the reward table instead
Racing::EPData[EPMap].maxTime = race["TimeLimit"];
// max score is specified in the XDT, but can be updated if specified in the drops JSON
epInfo.maxScore = (int)race["ScoreCap"];
// time limit and total pods are not stored in the XDT, so we include it in the drops JSON
epInfo.maxTime = (int)race["TimeLimit"];
epInfo.maxPods = (int)race["TotalPods"];
// IZ-specific calculated constants included in the drops JSON
epInfo.scaleFactor = (double)race["ScaleFactor"];
epInfo.podFactor = (double)race["PodFactor"];
epInfo.timeFactor = (double)race["TimeFactor"];
// score cutoffs // score cutoffs
std::vector<int> rankScores; std::vector<int> rankScores;
@ -695,7 +686,7 @@ static void loadEggs(json& eggData, int32_t* nextId) {
} }
} }
/* /*
* Load gruntwork output, if it exists * Load gruntwork output, if it exists
*/ */
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) { static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
@ -1370,7 +1361,7 @@ void TableData::flush() {
targetIDs.push_back(tID); targetIDs.push_back(tID);
for (int32_t tType : path.targetTypes) for (int32_t tType : path.targetTypes)
targetTypes.push_back(tType); targetTypes.push_back(tType);
pathObj["iBaseSpeed"] = path.speed; pathObj["iBaseSpeed"] = path.speed;
pathObj["iTaskID"] = path.escortTaskID; pathObj["iTaskID"] = path.escortTaskID;
pathObj["bRelative"] = path.isRelative; pathObj["bRelative"] = path.isRelative;

View File

@ -64,7 +64,7 @@ void terminate(int arg) {
} }
#ifdef _WIN32 #ifdef _WIN32
static BOOL WINAPI winTerminate(DWORD arg) { static BOOL winTerminate(DWORD arg) {
terminate(0); terminate(0);
return FALSE; return FALSE;
} }

2
tdata

@ -1 +1 @@
Subproject commit 8c98c8368243a6e2a10cc5fe273236754f896e6a Subproject commit cc65dbb402b5baa2b604ed66132edd88cc82a52a