2 Commits

Author SHA1 Message Date
FinnHornhoover
50df74212b Merge f4b36b8f73 into 70a27afad1 2023-10-08 17:02:43 -04:00
FinnHornhoover
f4b36b8f73 added alternate racing score funcitonality 2023-07-24 00:33:19 +03:00
15 changed files with 89 additions and 147 deletions

View File

@@ -9,13 +9,12 @@ on:
- CMakeLists.txt - CMakeLists.txt
- Makefile - Makefile
pull_request: pull_request:
types: [opened, reopened, synchronize, ready_for_review] types: ready_for_review
paths: paths:
- src/** - src/**
- vendor/** - vendor/**
- CMakeLists.txt - CMakeLists.txt
- Makefile - Makefile
workflow_dispatch:
jobs: jobs:
ubuntu-build: ubuntu-build:

3
.gitmodules vendored
View File

@@ -1,6 +1,3 @@
[submodule "tdata"] [submodule "tdata"]
path = tdata path = tdata
url = https://github.com/OpenFusionProject/tabledata.git url = https://github.com/OpenFusionProject/tabledata.git
[submodule "vendor/Lua"]
path = vendor/Lua
url = https://github.com/walterschell/Lua.git

View File

@@ -34,28 +34,20 @@ else()
set(BIN_NAME fusion) set(BIN_NAME fusion)
endif() endif()
# add lua
option(LUA_BUILD_COMPILER OFF)
option(LUA_ENABLE_SHARED OFF)
option(LUA_ENABLE_TESTING OFF)
add_subdirectory(vendor/Lua)
include_directories(src vendor) include_directories(src vendor)
file(GLOB_RECURSE VENDOR_SOURCES vendor/bcrypt/**.[ch] vendor/mingw/**.h vendor/INIReader.hpp vendor/JSON.hpp) file(GLOB_RECURSE SOURCES src/**.[ch]pp vendor/**.[ch]pp vendor/**.[ch] version.h)
file(GLOB_RECURSE SOURCES src/**.[ch]pp version.h)
configure_file(version.h.in ${CMAKE_SOURCE_DIR}/version.h @ONLY) configure_file(version.h.in ${CMAKE_SOURCE_DIR}/version.h @ONLY)
add_executable(openfusion ${SOURCES} ${VENDOR_SOURCES}) add_executable(openfusion ${SOURCES})
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME}) set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
# find sqlite3 and use it # find sqlite3 and use it
find_package(SQLite3 REQUIRED) find_package(SQLite3 REQUIRED)
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS}) target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
target_link_libraries(openfusion PRIVATE lua_static ${SQLite3_LIBRARIES}) target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
# Makes it so config, tdata, etc. get picked up when starting via the debugger in VS # Makes it so config, tdata, etc. get picked up when starting via the debugger in VS
set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")

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

@@ -94,49 +94,20 @@ void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t si
static void npcBarkHandler(CNSocket* sock, CNPacketData* data) { static void npcBarkHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_BARKER* req = (sP_CL2FE_REQ_BARKER*)data->buf; sP_CL2FE_REQ_BARKER* req = (sP_CL2FE_REQ_BARKER*)data->buf;
int taskID = req->iMissionTaskID; // get bark IDs from task data
// ignore req->iNPC_ID as it is often fixated on a single npc in the region TaskData* td = Missions::Tasks[req->iMissionTaskID];
std::vector<int> barks;
if (Missions::Tasks.find(taskID) == Missions::Tasks.end()) { for (int i = 0; i < 4; i++) {
std::cout << "mission task not found: " << taskID << std::endl; if (td->task["m_iHBarkerTextID"][i] != 0) // non-zeroes only
return; barks.push_back(td->task["m_iHBarkerTextID"][i]);
} }
TaskData* td = Missions::Tasks[taskID]; if (barks.empty())
auto& barks = td->task["m_iHBarkerTextID"]; return; // no barks
Player* plr = PlayerManager::getPlayer(sock);
std::vector<std::pair<int32_t, int32_t>> npcLines;
for (Chunk* chunk : plr->viewableChunks) {
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
if (ent->kind != EntityKind::SIMPLE_NPC)
continue;
BaseNPC* npc = (BaseNPC*)ent->getEntity();
if (npc->type < 0 || npc->type >= NPCData.size())
continue; // npc unknown ?!
int barkType = NPCData[npc->type]["m_iBarkerType"];
if (barkType < 1 || barkType > 4)
continue; // no barks
int barkID = barks[barkType - 1];
if (barkID == 0)
continue; // no barks
npcLines.push_back(std::make_pair(npc->id, barkID));
}
}
if (npcLines.size() == 0)
return; // totally no barks
auto& [npcID, missionStringID] = npcLines[Rand::rand(npcLines.size())];
INITSTRUCT(sP_FE2CL_REP_BARKER, resp); INITSTRUCT(sP_FE2CL_REP_BARKER, resp);
resp.iNPC_ID = npcID; resp.iNPC_ID = req->iNPC_ID;
resp.iMissionStringID = missionStringID; resp.iMissionStringID = barks[Rand::rand(barks.size())];
sock->sendPacket(resp, P_FE2CL_REP_BARKER); sock->sendPacket(resp, P_FE2CL_REP_BARKER);
} }

View File

@@ -101,16 +101,26 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
EPInfo& epInfo = EPData[mapNum]; EPInfo& epInfo = EPData[mapNum];
EPRace& epRace = EPRaces[sock]; 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; uint64_t now = getTime() / 1000;
int timeDiff = now - epRace.startTime; int timeDiff = now - epRace.startTime;
int podsCollected = epRace.collectedRings.size(); int podsCollected = epRace.collectedRings.size();
int score = 0, fm = 0;
int score = std::min(epInfo.maxScore, (int)std::exp( if (useOGScoring) {
score = std::min(epInfo.maxScore, (int)std::exp(
(epInfo.podFactor * podsCollected) / epInfo.maxPods (epInfo.podFactor * podsCollected) / epInfo.maxPods
- (epInfo.timeFactor * timeDiff) / epInfo.maxTime - (epInfo.timeFactor * timeDiff) / epInfo.maxTime
+ epInfo.scaleFactor)); + epInfo.scaleFactor));
int fm = (1.0 + std::exp(epInfo.scaleFactor - 1.0) * epInfo.podFactor * podsCollected) / epInfo.maxPods; 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;
}
// we submit the ranking first... // we submit the ranking first...
Database::RaceRanking postRanking = {}; Database::RaceRanking postRanking = {};

View File

@@ -9,9 +9,9 @@
struct EPInfo { struct EPInfo {
// available through XDT (maxScore may be updated by drops) // available through XDT (maxScore may be updated by drops)
int zoneX, zoneY, EPID, maxScore; int zoneX, zoneY, EPID, maxScore;
// available through drops // (maybe) available through drops
int maxTime, maxPods; int maxTime = 0, maxPods = 0;
double scaleFactor, podFactor, timeFactor; double scaleFactor = 0.0, podFactor = 0.0, timeFactor = 0.0;
}; };
struct EPRace { struct EPRace {

View File

@@ -586,15 +586,33 @@ static void loadDrops(json& dropData) {
EPInfo& epInfo = Racing::EPData[EPMap]; EPInfo& epInfo = Racing::EPData[EPMap];
// max score is specified in the XDT, but can be updated if specified in the drops JSON // time limit isn't stored in the XDT, so we include it in the reward table instead
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.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"]; epInfo.maxPods = (int)race["TotalPods"];
// IZ-specific calculated constants included in the drops JSON }
// update scale factor (if present)
if (race.find("ScaleFactor") != race.end()) {
epInfo.scaleFactor = (double)race["ScaleFactor"]; epInfo.scaleFactor = (double)race["ScaleFactor"];
}
// update pod factor (if present)
if (race.find("PodFactor") != race.end()) {
epInfo.podFactor = (double)race["PodFactor"]; epInfo.podFactor = (double)race["PodFactor"];
}
// update time factor (if present)
if (race.find("TimeFactor") != race.end()) {
epInfo.timeFactor = (double)race["TimeFactor"]; epInfo.timeFactor = (double)race["TimeFactor"];
}
// score cutoffs // score cutoffs
std::vector<int> rankScores; std::vector<int> rankScores;

View File

@@ -1,30 +0,0 @@
#include "lua/Manager.hpp"
#include <iostream>
using namespace LuaManager;
static lua_State *globalState = nullptr;
void LuaManager::init() {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
static LuaThread *startScript(const char *script) {
lua_State *L = lua_newthread(globalState);
LuaThread *T = new LuaThread(L, luaL_ref(L, LUA_REGISTRYINDEX));
if (luaL_dostring(L, script)) {
std::cout << "Lua error: " << lua_tostring(L, -1) << std::endl;
lua_pop(L, 1);
}
return T;
}
void LuaManager::init() {
globalState = luaL_newstate();
luaL_openlibs(globalState);
delete startScript("print(\"Hello from Lua!\")");
}

View File

@@ -1,7 +0,0 @@
#pragma once
#include "lua/Thread.hpp"
namespace LuaManager {
void init();
}

View File

@@ -1,17 +0,0 @@
#pragma once
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
struct LuaThread {
lua_State *L;
int ref;
LuaThread(lua_State *L, int ref) : L(L), ref(ref) {}
~LuaThread() {
luaL_unref(L, LUA_REGISTRYINDEX, ref);
}
};

View File

@@ -24,8 +24,6 @@
#include "Eggs.hpp" #include "Eggs.hpp"
#include "Rand.hpp" #include "Rand.hpp"
#include "lua/Manager.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "sandbox/Sandbox.hpp" #include "sandbox/Sandbox.hpp"
@@ -66,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;
} }
@@ -140,7 +138,6 @@ int main() {
Trading::init(); Trading::init();
Database::open(); Database::open();
LuaManager::init();
switch (settings::EVENTMODE) { switch (settings::EVENTMODE) {
case 0: break; // no event case 0: break; // no event

2
tdata

Submodule tdata updated: 8c98c83682...cc65dbb402

1
vendor/Lua vendored

Submodule vendor/Lua deleted from 88246d621a