12 Commits

Author SHA1 Message Date
d32dd4c319 added startScript() 2023-12-30 01:53:07 -06:00
c5de584762 define LuaManager::init 2023-12-30 01:00:27 -06:00
450cc78c9a added globalState; minor styling fixes 2023-12-30 00:56:06 -06:00
95f2920f5c start LuaManager
for now it runs a simple test script on init()
2023-12-13 16:42:40 -06:00
27ccda5b10 added Lua to vendor sources
- TODO: add Lua to our makefile eventually
2023-12-13 16:33:32 -06:00
FinnHornhoover
d9ebb4e3ef only allow barkers of simple npcs
Co-authored-by: Gent Semaj <gsemaj@proton.me>
2023-12-12 06:01:30 -08:00
FinnHornhoover
73c610b471 randomized npcs for mission barkers 2023-12-12 06:01:30 -08:00
FinnHornhoover
3e6bfea3fe fixed unexpected randomization of barkers 2023-12-12 06:01:30 -08:00
FinnHornhoover
cd265af8e0 Original Racing Scores Functionality (#257) 2023-10-12 04:42:57 +02:00
CakeLancelot
38c68f351b Update README.md to fix some inaccurate info, move commands to wiki page 2023-10-12 04:00:16 +02:00
edfbe4d005 Make winTerminate() a WINAPI function
This macro makes it use the stdcall calling convention when compiling
for 32-bit Windows. Its absence was technically a bug, but it's a no-op
on 64-bit Windows.
2023-10-12 02:02:23 +02:00
96c430c994 Restore rapid-fire anti-cheat for projectiles
For a while we had made the temporary rapid-fire anti-cheat optional on
master, but had removed it entirely on the refactor branch. The
modification on master was acidentally only applied to the regular
(non-projectile) combat handler, while the removal on refactor removed
both that and the projectile check.

When the refactor branch was merged, that resulted in the removal of
only the projectile rapid-fire check, while the conditional regular
combat rapid-fire check was kept.

This change restores the projectile rapid-fire check such that it is
conditional, just like for regular combat.
2023-10-12 02:02:23 +02:00
17 changed files with 145 additions and 78 deletions

3
.gitmodules vendored
View File

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

View File

@@ -34,20 +34,28 @@ else()
set(BIN_NAME fusion)
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)
file(GLOB_RECURSE SOURCES src/**.[ch]pp vendor/**.[ch]pp vendor/**.[ch] version.h)
file(GLOB_RECURSE VENDOR_SOURCES vendor/bcrypt/**.[ch] vendor/mingw/**.h vendor/INIReader.hpp vendor/JSON.hpp)
file(GLOB_RECURSE SOURCES src/**.[ch]pp version.h)
configure_file(version.h.in ${CMAKE_SOURCE_DIR}/version.h @ONLY)
add_executable(openfusion ${SOURCES})
add_executable(openfusion ${SOURCES} ${VENDOR_SOURCES})
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
# find sqlite3 and use it
find_package(SQLite3 REQUIRED)
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
target_link_libraries(openfusion PRIVATE lua_static ${SQLite3_LIBRARIES})
# 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}")

View File

@@ -28,8 +28,7 @@ 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).
### 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.
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.
@@ -54,10 +53,7 @@ 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 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`.
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.
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.
@@ -66,7 +62,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").
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 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!).
It instead loads the web pages locally using the `file://` schema, and fetches the game's assets from a standard web server.
`/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.
@@ -102,26 +98,13 @@ If you'd like to contribute to this project, please read [CONTRIBUTING.md](CONTR
## Gameplay
The goal of the project is to faithfully recreate the game as it was at the time of the targeted build.
The server is not yet complete, however, and some functionality is still missing.
While most features are implemented and the game is playable start to finish, there may be missing functionality or bugs present.
Because the server is still in development, ordinary players are allowed access to a few admin commands:
Depending on the server configuration, you'll have access to certain commands.
![](res/sane_upsell.png)
For the public servers: Original has item spawning, the ability to set player speed/jump height, and teleportation enabled (default account level 50).
Meanwhile the Academy server is more meant for legitimate playthroughs (default account level 99).
### 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.).
When hosting a local server, you will have access to all commands by default (account level 1).
### 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).
For a list of available commands, see [this wiki page](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list).

View File

@@ -51,11 +51,6 @@ motd=Welcome to OpenFusion!
# and pre-Academy builds must *not* contain it.
#enabledpatches=1013
# Use Original FusionFall's racing score and reward calculation?
# Set false to use Retro's calculation, make sure you have the correct
# patch(es) loaded.
#ogracingscores=true
# xdt json filename
#xdtdata=xdt.json
# NPC json filename

View File

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

View File

@@ -94,20 +94,49 @@ void NPCManager::sendToViewable(Entity *npc, void *buf, uint32_t type, size_t si
static void npcBarkHandler(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_BARKER* req = (sP_CL2FE_REQ_BARKER*)data->buf;
// get bark IDs from task data
TaskData* td = Missions::Tasks[req->iMissionTaskID];
std::vector<int> barks;
for (int i = 0; i < 4; i++) {
if (td->task["m_iHBarkerTextID"][i] != 0) // non-zeroes only
barks.push_back(td->task["m_iHBarkerTextID"][i]);
int taskID = req->iMissionTaskID;
// ignore req->iNPC_ID as it is often fixated on a single npc in the region
if (Missions::Tasks.find(taskID) == Missions::Tasks.end()) {
std::cout << "mission task not found: " << taskID << std::endl;
return;
}
if (barks.empty())
return; // no barks
TaskData* td = Missions::Tasks[taskID];
auto& barks = td->task["m_iHBarkerTextID"];
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);
resp.iNPC_ID = req->iNPC_ID;
resp.iMissionStringID = barks[Rand::rand(barks.size())];
resp.iNPC_ID = npcID;
resp.iMissionStringID = missionStringID;
sock->sendPacket(resp, P_FE2CL_REP_BARKER);
}

View File

@@ -105,18 +105,12 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
uint64_t now = getTime() / 1000;
int timeDiff = now - epRace.startTime;
int podsCollected = epRace.collectedRings.size();
int score = 0, fm = 0;
if (settings::OGRACINGSCORES) {
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 score = std::min(epInfo.maxScore, (int)std::exp(
(epInfo.podFactor * podsCollected) / epInfo.maxPods
- (epInfo.timeFactor * timeDiff) / epInfo.maxTime
+ epInfo.scaleFactor));
int fm = (1.0 + std::exp(epInfo.scaleFactor - 1.0) * epInfo.podFactor * podsCollected) / epInfo.maxPods;
// we submit the ranking first...
Database::RaceRanking postRanking = {};

View File

@@ -9,9 +9,9 @@
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;
// available through drops
int maxTime, maxPods;
double scaleFactor, podFactor, timeFactor;
};
struct EPRace {

View File

@@ -586,17 +586,15 @@ static void loadDrops(json& dropData) {
EPInfo& epInfo = Racing::EPData[EPMap];
// time limit isn't stored in the XDT, so we include it in the reward table instead
// 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"];
// the following has to be present based on the score calculation method
if (settings::OGRACINGSCORES) {
epInfo.maxScore = (int)race["ScoreCap"];
epInfo.maxPods = (int)race["TotalPods"];
epInfo.scaleFactor = (double)race["ScaleFactor"];
epInfo.podFactor = (double)race["PodFactor"];
epInfo.timeFactor = (double)race["TimeFactor"];
}
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
std::vector<int> rankScores;

30
src/lua/Manager.cpp Normal file
View File

@@ -0,0 +1,30 @@
#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!\")");
}

7
src/lua/Manager.hpp Normal file
View File

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

17
src/lua/Thread.hpp Normal file
View File

@@ -0,0 +1,17 @@
#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,6 +24,8 @@
#include "Eggs.hpp"
#include "Rand.hpp"
#include "lua/Manager.hpp"
#include "settings.hpp"
#include "sandbox/Sandbox.hpp"
@@ -64,7 +66,7 @@ void terminate(int arg) {
}
#ifdef _WIN32
static BOOL winTerminate(DWORD arg) {
static BOOL WINAPI winTerminate(DWORD arg) {
terminate(0);
return FALSE;
}
@@ -138,6 +140,7 @@ int main() {
Trading::init();
Database::open();
LuaManager::init();
switch (settings::EVENTMODE) {
case 0: break; // no event

View File

@@ -67,9 +67,6 @@ int settings::MONITORINTERVAL = 5000;
// event mode settings
int settings::EVENTMODE = 0;
// racing score mode
bool settings::OGRACINGSCORES = true;
void settings::init() {
INIReader reader("config.ini");
@@ -114,7 +111,6 @@ void settings::init() {
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
OGRACINGSCORES = reader.GetBoolean("shard", "ogracingscores", OGRACINGSCORES);
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);

View File

@@ -38,7 +38,6 @@ namespace settings {
extern int MONITORPORT;
extern int MONITORINTERVAL;
extern bool DISABLEFIRSTUSEFLAG;
extern bool OGRACINGSCORES;
void init();
}

2
tdata

Submodule tdata updated: cc65dbb402...8c98c83682

1
vendor/Lua vendored Submodule

Submodule vendor/Lua added at 88246d621a