Compare commits

...

22 Commits

Author SHA1 Message Date
github-actions
016a204499 v1.0.0 Build 11 2025-05-11 06:20:45 +00:00
Jacob Schmidt
461a678f8b feat: Implement timesheet submission and rating system in bank addon
All checks were successful
Build / Build (push) Successful in 25s
This commit introduces the ability for players to submit timesheets and receive payment based on their rating. It also includes updates to player data handling to incorporate the new rating system.

Key changes include:

- Added `getPlayer` PREP.
- Implemented `BANK::SUBMIT::TIMESHEET` case in `XEH_preInit_server.sqf` to handle timesheet submissions.
- Modified `fnc_initBankStore.sqf` to include rating in player data and update account information.
- Added `submitTimesheet` function to `fnc_initBankStore.sqf` to process timesheet submissions and pay players based on rating.
- Updated `handlePlayerLoad` and `updateAccount` functions in `fnc_initBankStore.sqf` to include rating.
- Added `getRating` function to `fnc_initBankStore.sqf` to retrieve player rating.
- Updated `BANK::HANDLE::PLAYER::LOAD` case in `XEH_preInit_server.sqf` to include rating value.
- Added database saving for reputation.

These changes introduce a new gameplay mechanic and provide a way for players to earn money based on their performance.
2025-05-11 01:19:58 -05:00
github-actions
fb7c198e56 v1.0.0 Build 10 2025-05-10 22:50:38 +00:00
Jacob Schmidt
2ff4a37677 Merge branch 'master' of https://gitea.innovativedevsolutions.org/IDSolutions/server
All checks were successful
Build / Build (push) Successful in 38s
2025-05-10 17:50:25 -05:00
Jacob Schmidt
991ee5019a feat: Enhance admin and bank event handling with improved logging
This commit refines the event handling for both admin and bank functionalities, ensuring better clarity and consistency in logging.

Key changes include:

- Updated event names in admin handling to follow a consistent naming convention.
- Improved parameter handling in admin event functions, ensuring validation checks are in place.
- Enhanced logging messages to provide clearer context regarding the source of events and actions taken.
- Introduced bank event handling for deposit, transfer, withdrawal, and balance inquiries, with appropriate validation and logging.

These changes aim to improve the maintainability and readability of the codebase while ensuring robust event management.
2025-05-10 17:50:21 -05:00
github-actions
17d5942de4 v1.0.0 Build 9 2025-05-05 01:03:33 +00:00
Jacob Schmidt
74b9f78141 Merge branch 'master' of https://gitea.innovativedevsolutions.org/IDSolutions/server
All checks were successful
Build / Build (push) Successful in 25s
2025-05-04 20:03:03 -05:00
Jacob Schmidt
db238970f7 feat: Update server mod icons and properties
This commit updates the server mod with new icons and properties.

The following changes were made:

- Updated `picture` to `title_ca.paa` in `mod.cpp`.
- Updated `logoSmall` to `icon_64_ca.paa` in `mod.cpp`.
- Updated `logo` to `icon_128_ca.paa` in `mod.cpp`.
- Updated `logoOver` to `icon_128_highlight_ca.paa` in `mod.cpp`.
- Added `dlcColor` to `mod.cpp`.
- Updated binary files for `icon_128_highlight_ca.paa`, `icon_64_ca.paa`, and `icon_128_ca.paa`.
2025-05-04 20:02:30 -05:00
github-actions
ba88f1ccbd v1.0.0 Build 8 2025-05-04 00:33:58 +00:00
Jacob Schmidt
eec98d03eb feat: Implement admin event handling and improve player data saving
All checks were successful
Build / Build (push) Successful in 25s
This commit introduces admin event handling to manage various administrative actions and enhances the player data saving process.

The following changes were made:

-   Implemented event handling for admin actions such as advancing funds, handling paydays, transferring funds, sending messages, and updating paygrades. These events are triggered via the `QGVAR(handleEvents)` event handler.
-   Added `initAdminStore` and `verifyAdminStore` functions to manage the admin store.
-   Refactored the `fnc_handleDisconnect.sqf` script to use the `GETVAR` macro for retrieving player data, ensuring consistency and readability.
-   Replaced hardcoded default values with `QUOTE()` wrapped values in `fnc_handleDisconnect.sqf` for better maintainability and configuration.
2025-05-03 19:33:45 -05:00
Jacob Schmidt
a1fc5090ad Removed sqf_validator.py tool 2025-04-05 14:30:14 -05:00
github-actions
c0ec6b4206 v1.0.0 Build 7 2025-04-05 19:25:07 +00:00
Jacob Schmidt
ca8fad855a Merge branch 'master' of https://gitea.innovativedevsolutions.org/IDSolutions/server
All checks were successful
Build / Build (push) Successful in 24s
2025-04-05 14:24:49 -05:00
Jacob Schmidt
257a62acc0 fix: Rename companyGenerals to companyShareholders
This commit renames the `companyGenerals` variable to `companyShareholders` to better reflect its intended purpose and usage within the codebase. The change affects the following files:

-   `addons/main/config.cpp`: Renames the `companyGenerals` config entry to `companyShareholders`.
-   `addons/init/functions/fnc_handleServerState.sqf`: Updates the variable name used when saving company state.
-   `addons/db/functions/fnc_loadGameState.sqf`: Updates the variable name used when loading company state.
-   `addons/db/functions/fnc_saveGameState.sqf`: Updates the variable name used when saving company state.
-   `addons/init/functions/fnc_handleServerStateLoad.sqf`: Updates the variable name used when loading company state from the server.

This change ensures consistency and clarity in the codebase, making it easier to understand and maintain. The `companyGarageUnlocks` variable was also removed from the save/load functions as it was not being used.
2025-04-05 14:24:46 -05:00
github-actions
8f33123e1d v1.0.0 Build 6 2025-04-05 19:16:49 +00:00
Jacob Schmidt
5b30efa3b0 feat: Refactor database system for improved persistence and functionality
All checks were successful
Build / Build (push) Successful in 26s
This commit refactors the database system to improve persistence, functionality, and code clarity. The key changes include:

-   **Removed direct store access:** Removed `createStore`, `getFromStore`, and `getStore` PREP macros.
-   **Centralized store management:** Introduced a central store registry (`FORGE_STORE_REG`) managed by the database interface.
-   **Namespace-based persistence:** Stores are now persisted in mission and profile namespaces instead of a global store.
-   **Simplified load/save functions:** `loadFromMission`, `loadFromProfile`, `saveToMission`, `saveToProfile`, and `saveToTemp` functions are updated to use the new namespace-based persistence. They now accept a `keyField` parameter for retrieving specific fields within a key's data.
-   **Refactored `processDBRequest`:** Updated to handle new request types and parameters, aligning with the refactored load/save functions.
-   **Improved error handling:** Added more robust error handling and logging, including checks for empty store names and missing keys.
-   **Removed client registration:** Removed client registration and cleanup logic as it's no longer needed with the new persistence model.
-   **Updated `verifyDB`:** Simplified to directly return the store registry.
-   **Updated `initDB`:** Refactored to use a HashMap object for the store interface and added more database functions.
-   **Added .gitignore entries:** Added entries for Visual Studio and other common build artifacts.
-   **Updated `loadGameState` and `saveGameState`:** Updated to support loading and saving game state to either mission or profile namespace.
2025-04-05 14:16:35 -05:00
github-actions
5b762b3ad0 v1.0.0 Build 5 2025-03-30 22:16:57 +00:00
Jacob Schmidt
3bd5a4a9bf feat: Implement store persistence and improve store interface
All checks were successful
Build / Build (push) Successful in 24s
This commit introduces store persistence and refactors the store interface for improved functionality. The changes include:

-   Adding a `save` function to persist store data.
-   Refactoring `createStore` to `create` and `getStore` to `get`.
-   Adding `set` function to create store if not exist.
-   Refactoring `set` to `hset` and `get` to `hget` for hash set and get operations.
-   Refactoring `delete` to `hdel` for hash delete operations.
-   Implementing store saving after each modification.
2025-03-30 17:15:28 -05:00
github-actions
767c95978e v1.0.0 Build 4 2025-03-28 14:46:30 +00:00
Jacob Schmidt
b4f30ef954 Merge branch 'master' of https://gitea.innovativedevsolutions.org/IDSolutions/server
All checks were successful
Build / Build (push) Successful in 25s
2025-03-28 09:46:12 -05:00
Jacob Schmidt
bec0adcdbf feat: Update build environment and add XEH PREP
This commit includes the following changes:

-   Updates the build environment in the GitHub Actions workflow to use `ubuntu-latest` instead of `ubuntu-22.04`.
-   Adds `playerGroup2Server` to the XEH_PREP.hpp file.
-   Updates the picture path in CfgMods.hpp to include the file extension.
2025-03-28 09:46:08 -05:00
94043520a5 Update .github/workflows/build.yml 2025-02-22 13:55:18 -06:00
50 changed files with 1827 additions and 226 deletions

View File

@ -16,7 +16,7 @@ on:
jobs:
build:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v4

405
.gitignore vendored
View File

@ -1,5 +1,410 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
# Hemtt
*.pbo
.hemttout
hemtt
hemtt.exe
*.biprivatekey
# Added by cargo
/target

View File

@ -1 +1,3 @@
PREP(initAdmin);
PREP(initAdmin);
PREP(initAdminStore);
PREP(verifyAdminStore);

View File

@ -1,3 +1,56 @@
#include "script_component.hpp"
call FUNC(initAdmin);
call FUNC(initAdmin);
call FUNC(initAdminStore);
[QGVAR(handleEvents), {
params ["_event", "_data"];
diag_log format ["[FORGE::Server::Admin::XEH_preInit] Received event: %1 with data: %2", _event, _data];
switch (_event) do {
case "ADMIN::ADVANCE::ALL": {
private _adminStore = call FUNC(verifyAdminStore);
_data params [["_amount", 0, [0]]];
if (_amount isEqualTo 0) exitWith { diag_log "[FORGE::Server::Admin::XEH_preInit::advanceAll] Invalid amount!"; };
_adminStore call ["handleTransfer", ["advanceAll", _amount]];
};
case "ADMIN::PAYDAY": {
private _adminStore = call FUNC(verifyAdminStore);
_adminStore call ["handleTransfer", ["payday"]];
};
case "ADMIN::TRANSFER": {
private _adminStore = call FUNC(verifyAdminStore);
_data params [["_condition", "", [""]], ["_amount", 0, [0]], ["_uid", "", [""]]];
if (_condition isEqualTo "" || _amount isEqualTo 0 || _uid isEqualTo "") exitWith { diag_log "[FORGE::Server::Admin::XEH_preInit::handleTransfer] Invalid condition, amount, or UID!"; };
_adminStore call ["handleTransfer", [_condition, _amount, _uid]];
};
case "ADMIN::SEND::MESSAGE": {
private _adminStore = call FUNC(verifyAdminStore);
_data params [["_uid", "", [""]], ["_message", "", [""]]];
if (_message isEqualTo "") exitWith { diag_log "[FORGE::Server::Admin::XEH_preInit::sendMessage] Invalid message!"; };
if (_uid isEqualTo "") then {
_adminStore call ["broadcastMessage", [_message]];
} else {
_adminStore call ["sendMessage", [_uid, _message]];
};
};
case "ADMIN::UPDATE::PAYGRADE": {
private _adminStore = call FUNC(verifyAdminStore);
_data params [["_uid", "", [""]], ["_paygrade", "", [""]]];
if (_uid isEqualTo "" || _paygrade isEqualTo "") exitWith { diag_log "[FORGE::Server::Admin::XEH_preInit::updatePaygrade] Invalid UID or paygrade!"; };
_adminStore call ["updatePaygrade", [_uid, _paygrade]];
};
default {
diag_log format ["[FORGE::Server::Admin::XEH_preInit] Unknown event: %1 with data: %2", _event, _data];
};
};
}] call CFUNC(addEventHandler);

View File

@ -13,6 +13,6 @@
_cpof allowDamage false;
_cpof setVariable ["isCPOF", true, true];
diag_log text format ["[FORGE Admin] ClassName: '%1' Pos: '%2' Dir: '%3'", _className, _pos, _dir];
diag_log text format ["[FORGE::Server::Admin::initAdmin] ClassName: '%1' Pos: '%2' Dir: '%3'", _className, _pos, _dir];
} forEach ("true" configClasses (missionConfigFile >> "CfgCpofs" >> "cpofs"));

View File

@ -0,0 +1,119 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_admin_fnc_initAdminStore
* Author: IDSolutions
*
* Description:
* Initializes a server-side admin store interface for managing admin operations
* Provides CRUD operations for admin data, including database persistence through ArmaDragonflyClient
*
* Creates a hashMap object with methods for:
* - Sending messages to players
* - Managing player data
* - Performing administrative tasks
*
* Returns: <HASHMAP>
*
* Example:
* private _adminStore = call forge_server_admin_fnc_initAdminStore;
* _adminStore call ["sendMessage", [getPlayerUID player, "Hello, this is a test message"]];
*/
private _adminStore = createHashMapObject [[
["#type", "IAdminStore"],
["broadcastMessage", {
params ["_message"];
[format ["Incoming Message from Field Commander: <br/>%1", _message], "warning", 5] remoteExec ["forge_client_misc_fnc_notify", 0];
}],
["sendMessage", {
params ["_uid", "_message"];
private _target = objNull;
{
if (getPlayerUID _x == _uid) exitWith { _target = _x; };
} forEach allPlayers;
if (!isNull _target) then {
[format ["Incoming Message from Field Commander: <br/>%1", _message], "warning", 5] remoteExec ["forge_client_misc_fnc_notify", _target];
};
}],
["updatePaygrade", {
params ["_uid", "_paygrade"];
private _target = objNull;
{
if (getPlayerUID _x == _uid) exitWith { _target = _x; };
} forEach allPlayers;
if (!isNull _target) then { SETPVAR(_target,FORGE_PayGrade,_paygrade); };
}],
["handleTransfer", {
params ["_condition", "_amount", "_uid"];
switch (_condition) do {
case ("advance"): {
private _target = objNull;
{
if (getPlayerUID _x == _uid) exitWith { _target = _x; };
} forEach allPlayers;
if (isNull _target) exitWith {};
private _bank = GETVAR(_target,FORGE_Bank,0);
private _newBalance = _bank + _amount;
SETPVAR(_target,FORGE_Bank,_newBalance);
};
case ("advanceAll"): {
{
private _player = _x;
private _bank = GETVAR(_player,FORGE_Bank,0);
private _newBalance = _bank + _amount;
SETPVAR(_player,FORGE_Bank,_newBalance);
} forEach allPlayers;
};
case ("deduct"): {
private _target = objNull;
{
if (getPlayerUID _x == _uid) exitWith { _target = _x; };
} forEach allPlayers;
if (isNull _target) exitWith {};
private _bank = GETVAR(_target,FORGE_Bank,0);
private _newBalance = _bank - _amount;
if (_newBalance < 0) then { _newBalance = 0; };
SETPVAR(_target,FORGE_Bank,_newBalance);
};
case ("payday"): {
private _payGrades = (missionConfigFile >> "CfgPaygrades" >> "payGrades") call BIS_fnc_getCfgData;
{
private _player = _x;
private _payGrade = GETVAR(_player,FORGE_PayGrade,nil);
{
_x params ["_payGradeIndex", "_payGradeBonus"];
if (_payGradeIndex == _payGrade) then {
private _bank = GETVAR(_player,FORGE_Bank,0);
private _newBalance = _bank + _payGradeBonus;
SETPVAR(_player,FORGE_Bank,_newBalance);
};
} forEach _payGrades;
} forEach allPlayers;
};
};
}]
]];
SETMVAR(FORGE_ADMIN_STORE_REG,_adminStore);
GETMVAR(FORGE_ADMIN_STORE_REG,_adminStore);

View File

@ -0,0 +1,32 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_admin_fnc_verifyAdminStore
* Author: IDSolutions
*
* [Description]
* Ensures the admin store is initialized and returns the store object.
* Acts as a singleton accessor for the admin store interface.
* If the store doesn't exist yet, it initializes it first.
*
* Arguments:
* None
*
* Return Value:
* Admin Store <OBJECT> - The admin store interface object
*
* Example:
* private _adminStore = call forge_server_admin_fnc_verifyAdminStore
*
* Public: No
*/
private _store = GETMVAR(FORGE_ADMIN_STORE_REG,nil);
if (isNil "_store") then {
_store = call FUNC(initAdminStore);
diag_log text "[FORGE::Server::Admin::verifyAdminStore] Admin store initialized";
};
_store

View File

@ -1 +1,3 @@
PREP(initBank);
PREP(initBank);
PREP(initBankStore);
PREP(verifyBankStore);

View File

@ -1,3 +1,78 @@
#include "script_component.hpp"
call FUNC(initBank);
call FUNC(initBank);
call FUNC(initBankStore);
[QGVAR(handleEvents), {
params ["_event", "_data"];
diag_log text format ["[FORGE::Server::Bank::XEH_preInit] Received event: '%1' with data: '%2'", _event, _data];
switch (_event) do {
case "BANK::DEPOSIT": {
private _bankStore = call FUNC(verifyBankStore);
_data params [["_uid", "", [""]], ["_amount", 0, [0]]];
if (_uid isEqualTo "" || _amount isEqualTo 0) exitWith { diag_log "[FORGE::Server::Bank::XEH_preInit::handleDeposit] Invalid UID and amount, UID cannot be empty and amount must be greater than 0!"; };
_bankStore call ["deposit", [_uid, _amount]];
};
case "BANK::SUBMIT::TIMESHEET": {
private _bankStore = call FUNC(verifyBankStore);
_data params [["_uid", "", [""]], ["_rating", 0, [0]]];
if (_uid isEqualTo "" || _rating <= 0) exitWith { diag_log "[FORGE::Server::Bank::XEH_preInit::handleSubmitTimesheet] Invalid UID, UID cannot be empty!"; };
_bankStore call ["submitTimesheet", [_uid, _rating]];
};
case "BANK::TRANSFER": {
private _bankStore = call FUNC(verifyBankStore);
_data params [["_fromUid", "", [""]], ["_toUid", "", [""]], ["_amount", 0, [0]]];
if (_fromUid isEqualTo "" || _toUid isEqualTo "" || _amount isEqualTo 0) exitWith { diag_log "[FORGE::Server::Bank::XEH_preInit::handleTransfer] Invalid UIDs and amount, UID cannot be empty and amount must be greater than 0!"; };
_bankStore call ["transfer", [_fromUid, _toUid, _amount]];
};
case "BANK::WITHDRAW": {
private _bankStore = call FUNC(verifyBankStore);
_data params [["_uid", "", [""]], ["_amount", 0, [0]]];
if (_uid isEqualTo "" || _amount isEqualTo 0) exitWith { diag_log "[FORGE::Server::Bank::XEH_preInit::handleWithdraw] Invalid UID and amount!"; };
_bankStore call ["withdraw", [_uid, _amount]];
};
case "BANK::GET::BALANCE": {
private _bankStore = call FUNC(verifyBankStore);
_data params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith { diag_log "[FORGE::Server::Bank::XEH_preInit::handleGetBalance] Invalid UID, UID cannot be empty!"; };
_bankStore call ["getBalance", [_uid]];
};
case "BANK::GET::CASH": {
private _bankStore = call FUNC(verifyBankStore);
_data params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith { diag_log "[FORGE::Server::Bank::XEH_preInit::handleGetCash] Invalid UID, UID cannot be empty!"; };
_bankStore call ["getCash", [_uid]];
};
case "BANK::HANDLE::PLAYER::LOAD": {
private _bankStore = call FUNC(verifyBankStore);
_data params [["_uid", "", [""]], ["_bankValue", 0, [0]], ["_cashValue", 0, [0]], ["_ratingValue", 0, [0]]];
if (_uid isEqualTo "") exitWith { diag_log "[FORGE::Server::Bank::XEH_preInit::handlePlayerLoad] Invalid UID, UID cannot be empty!"; };
_bankStore call ["handlePlayerLoad", [_uid, _bankValue, _cashValue, _ratingValue]];
};
case "BANK::GET::TRANSACTION::HISTORY": {
private _bankStore = call FUNC(verifyBankStore);
_data params [["_uid", "", [""]], ["_limit", 10, [0]]];
_bankStore call ["getTransactionHistory", [_uid, _limit]];
};
default {
diag_log format ["[FORGE::Server::Bank::XEH_preInit] Unknown event: %1 with data: %2", _event, _data];
};
};
}] call CFUNC(addEventHandler);

View File

@ -26,5 +26,5 @@
_bank setVariable ["BIS_enableRandomization", false];
};
diag_log text format ["[FORGE Bank] ClassName: '%1' Pos: '%2' Dir: '%3'", _className, _pos, _dir];
diag_log text format ["[FORGE::Server::Bank::initBank] ClassName: '%1' Pos: '%2' Dir: '%3'", _className, _pos, _dir];
} forEach ("true" configClasses (missionConfigFile >> "CfgBanks" >> "banks"));

View File

@ -0,0 +1,227 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Initializes a server-side bank store interface for managing banking operations.
* Provides CRUD operations for bank data, including database persistence.
*
* Arguments:
* None
*
* Return Value:
* <HASHMAP> Bank store object
*
* Example:
* private _bankStore = call forge_server_bank_fnc_initBank;
* _bankStore call ["deposit", [getPlayerUID player, 1000]];
*
* Public: Yes
*/
private _bankStore = createHashMapObject [[
["#type", "IBankStore"],
["#create", {
_self set ["accounts", createHashMap];
_self set ["transactions", []];
_self set ["isLoaded", true];
true
}],
["handlePlayerLoad", {
params [["_uid", "", [""]], ["_bankValue", 0, [0]], ["_cashValue", 0, [0]], ["_ratingValue", 0, [0]]];
if (_uid isEqualTo "") exitWith { false };
_self call ["updateAccount", [_uid, _bankValue, _cashValue, _ratingValue]];
diag_log text format ["[FORGE::Server::Bank::Store::handlePlayerLoad] Loaded player data for '%1' - Bank: '%2', Cash: '%3', Rating: '%4'", _uid, _bankValue, _cashValue, _ratingValue];
true
}],
["updateAccount", {
params [["_uid", "", [""]], ["_bankValue", 0, [0]], ["_cashValue", 0, [0]], ["_ratingValue", 0, [0]], ["_isSave", false, [false]]];
if (_uid isEqualTo "") exitWith { false };
private _accounts = _self get "accounts";
private _account = _accounts getOrDefault [_uid, createHashMap];
private _player = [_uid] call EFUNC(misc,getPlayer);
_account set ["bank", _bankValue];
_account set ["cash", _cashValue];
_account set ["rating", _ratingValue];
_accounts set [_uid, _account];
_self set ["accounts", _accounts];
private _balance = _account getOrDefault ["bank", 0];
private _cash = _account getOrDefault ["cash", 0];
SETPVAR(_player,FORGE_Bank,_balance);
SETPVAR(_player,FORGE_Cash,_cash);
SETPVAR(_player,FORGE_Rating,_ratingValue);
if (_isSave) then {
["hsetid", _uid, "bank", -1, [_bankValue]] call dragonfly_db_fnc_addTask;
["hsetid", _uid, "cash", -1, [_cashValue]] call dragonfly_db_fnc_addTask;
["hsetid", _uid, "reputation", -1, [_ratingValue]] call dragonfly_db_fnc_addTask;
};
diag_log text format ["[FORGE::Server::Bank::Store::updateAccount] Updated player data for '%1' - Bank: '%2', Cash: '%3', Rating: '%4'", _uid, _bankValue, _cashValue, _ratingValue];
true
}],
["getBalance", {
params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith { 0 };
private _accounts = _self get "accounts";
private _account = _accounts getOrDefault [_uid, createHashMap];
_account getOrDefault ["bank", 0]
}],
["getCash", {
params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith { 0 };
private _accounts = _self get "accounts";
private _account = _accounts getOrDefault [_uid, createHashMap];
_account getOrDefault ["cash", 0]
}],
["getRating", {
params [["_uid", "", [""]]];
if (_uid isEqualTo "") exitWith { 0 };
private _accounts = _self get "accounts";
private _account = _accounts getOrDefault [_uid, createHashMap];
_account getOrDefault ["rating", 0]
}],
["deposit", {
params [["_uid", "", [""]], ["_amount", 0, [0]]];
if (_uid isEqualTo "" || _amount <= 0) exitWith { false };
private _accounts = _self get "accounts";
private _account = _accounts getOrDefault [_uid, createHashMap];
private _currentCash = _account getOrDefault ["cash", 0];
private _currentBalance = _account getOrDefault ["bank", 0];
private _currentRating = _account getOrDefault ["rating", 0];
if (_currentCash < _amount) exitWith { false };
private _newBalance = _currentBalance + _amount;
private _newCash = _currentCash - _amount;
_self call ["updateAccount", [_uid, _newBalance, _newCash, _currentRating, true]];
private _transactions = _self get "transactions";
_transactions pushBack [_uid, "deposit", _amount, time];
_self set ["transactions", _transactions];
true
}],
["submitTimesheet", {
params [["_uid", "", [""]], ["_rating", 0, [0]]];
if (_uid isEqualTo "" || _rating <= 0) exitWith { false };
private _accounts = _self get "accounts";
private _account = _accounts getOrDefault [_uid, createHashMap];
private _currentBalance = _account getOrDefault ["bank", 0];
private _currentCash = _account getOrDefault ["cash", 0];
private _payMultiplyer = "MULTIPLYR" call BFUNC(getParamValue);
private _multiplyer = _rating * _payMultiplyer;
private _newBalance = _currentBalance + _multiplyer;
_self call ["updateAccount", [_uid, _newBalance, _currentCash, 0, true]];
private _transactions = _self get "transactions";
_transactions pushBack [_uid, "timesheet", _multiplyer, time];
_self set ["transactions", _transactions];
true
}],
["transfer", {
params [["_fromUid", "", [""]], ["_toUid", "", [""]], ["_amount", 0, [0]]];
if (_fromUid isEqualTo "" || _toUid isEqualTo "" || _amount <= 0) exitWith { false };
private _accounts = _self get "accounts";
private _fromAccount = _accounts getOrDefault [_fromUid, createHashMap];
private _toAccount = _accounts getOrDefault [_toUid, createHashMap];
private _fromBalance = _fromAccount getOrDefault ["bank", 0];
private _toBalance = _toAccount getOrDefault ["bank", 0];
private _fromCash = _fromAccount getOrDefault ["cash", 0];
private _toCash = _toAccount getOrDefault ["cash", 0];
private _fromRating = _fromAccount getOrDefault ["rating", 0];
private _toRating = _toAccount getOrDefault ["rating", 0];
if (_fromBalance < _amount) exitWith { false };
_self call ["updateAccount", [_fromUid, _fromBalance - _amount, _fromCash, _fromRating, true]];
_self call ["updateAccount", [_toUid, _toBalance + _amount, _toCash, _toRating, true]];
private _transactions = _self get "transactions";
_transactions pushBack [_fromUid, "transfer_out", _amount, time, _toUid];
_transactions pushBack [_toUid, "transfer_in", _amount, time, _fromUid];
_self set ["transactions", _transactions];
true
}],
["withdraw", {
params [["_uid", "", [""]], ["_amount", 0, [0]]];
if (_uid isEqualTo "" || _amount <= 0) exitWith { false };
private _accounts = _self get "accounts";
private _account = _accounts getOrDefault [_uid, createHashMap];
private _currentBalance = _account getOrDefault ["bank", 0];
private _currentCash = _account getOrDefault ["cash", 0];
private _currentRating = _account getOrDefault ["rating", 0];
if (_currentBalance < _amount) exitWith { false };
private _newBalance = _currentBalance - _amount;
private _newCash = _currentCash + _amount;
_self call ["updateAccount", [_uid, _newBalance, _newCash, _currentRating, true]];
private _transactions = _self get "transactions";
_transactions pushBack [_uid, "withdraw", _amount, time];
_self set ["transactions", _transactions];
true
}],
["getTransactionHistory", {
params [["_uid", "", [""]], ["_limit", 10, [0]]];
private _transactions = _self get "transactions";
private _filteredTransactions = [];
if (_uid isEqualTo "") then {
_filteredTransactions = _transactions;
} else {
{
if ((_x select 0) == _uid) then { _filteredTransactions pushBack _x; };
} forEach _transactions;
};
_filteredTransactions sort false;
_filteredTransactions resize (_limit min (count _filteredTransactions));
_filteredTransactions
}]
]];
SETMVAR(FORGE_BANK_STORE_REG,_bankStore);
GETMVAR(FORGE_BANK_STORE_REG,_bankStore);

View File

@ -0,0 +1,32 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_bank_fnc_verifyBankStore
* Author: IDSolutions
*
* [Description]
* Ensures the bank store is initialized and returns the store object.
* Acts as a singleton accessor for the bank store interface.
* If the store doesn't exist yet, it initializes it first.
*
* Arguments:
* None
*
* Return Value:
* Bank Store <OBJECT> - The bank store interface object
*
* Example:
* private _bankStore = call forge_server_bank_fnc_verifyBankStore
*
* Public: No
*/
private _store = GETMVAR(FORGE_BANK_STORE_REG,nil);
if (isNil "_store") then {
_store = call FUNC(initBankStore);
diag_log text "[FORGE::Server::Bank::verifyBankStore] Bank store initialized";
};
_store

1
addons/db/$PBOPREFIX$ Normal file
View File

@ -0,0 +1 @@
z\forge_server\addons\db

View File

@ -0,0 +1,19 @@
class Extended_PreStart_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preStart));
};
};
class Extended_PreInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_preInit));
serverInit = QUOTE(call COMPILE_FILE(XEH_preInit_server));
};
};
class Extended_PostInit_EventHandlers {
class ADDON {
init = QUOTE(call COMPILE_FILE(XEH_postInit));
clientInit = QUOTE(call COMPILE_FILE(XEH_postInit_client));
};
};

12
addons/db/XEH_PREP.hpp Normal file
View File

@ -0,0 +1,12 @@
PREP(initDB);
PREP(loadFromMission);
PREP(loadFromProfile);
PREP(loadFromTemp);
PREP(loadGameState);
PREP(loadPlayerState);
PREP(processDBRequest);
PREP(saveGameState);
PREP(saveToMission);
PREP(saveToProfile);
PREP(saveToTemp);
PREP(verifyDB);

View File

@ -0,0 +1,3 @@
#include "script_component.hpp"
call FUNC(initDB);

View File

@ -0,0 +1 @@
#include "script_component.hpp"

19
addons/db/XEH_preInit.sqf Normal file
View File

@ -0,0 +1,19 @@
#include "script_component.hpp"
ADDON = false;
PREP_RECOMPILE_START;
#include "XEH_PREP.hpp"
PREP_RECOMPILE_END;
SETMVAR(FORGE_STORE_REG,createHashMap);
// Process database requests
["forge_db_request", {
params ["_playerUID", "_type", "_name", "_key", "_keyField", "_data", "_requestID"];
private _result = [_type, _name, _key, _keyField, _data] call FUNC(processDBRequest);
["forge_db_response", [_playerUID, _type, _result, _requestID]] call CBA_fnc_globalEvent;
}] call CBA_fnc_addEventHandler;
ADDON = true;

View File

@ -0,0 +1 @@
#include "script_component.hpp"

View File

@ -0,0 +1,2 @@
#include "script_component.hpp"
#include "XEH_PREP.hpp"

16
addons/db/config.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "script_component.hpp"
class CfgPatches {
class ADDON {
name = COMPONENT_NAME;
units[] = {};
weapons[] = {};
requiredVersion = REQUIRED_VERSION;
requiredAddons[] = {"forge_server_main"};
authors[] = {"J. Schmidt", "Creedcoder"};
author = "J. Schmidt";
VERSION_CONFIG;
};
};
#include "CfgEventHandlers.hpp"

View File

@ -0,0 +1,226 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_initDB
* Author: J. Schmidt
*
* Description:
* Initializes the database system, creating the interface for other modules
*
* Arguments:
* None
*
* Return Value:
* Database interface object
*/
private _storeInterface = createHashMapObject [[
["#type", "IStore"],
["#create", {
private _storeRegistry = GETVAR(profileNamespace,FORGE_STORE_REG,createHashMap);
_self set ["stores", _storeRegistry];
true
}],
["_create", {
params [["_name", "", [""]]];
if (_name isEqualTo "") exitWith { ERROR_MSG("Store name cannot be empty"); nil };
private _stores = _self get "stores";
private _store = createHashMap;
_stores set [_name, _store];
_store
}],
["_read", {
params [["_name", "", [""]], ["_key", "", [""]]];
if (_name isEqualTo "") exitWith { ERROR_MSG("Store name cannot be empty"); nil };
private _stores = _self get "stores";
private _store = _stores get _name;
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found",_name); nil };
if (_key isEqualTo "") then { _store } else { _store get _key };
}],
["_write", {
params [["_name", "", [""]], ["_data", nil, ["", [], 0, true, createHashMap]]];
if (_name isEqualTo "" || isNil "_data") exitWith { ERROR_MSG("Store name and, or data cannot be empty"); nil };
private _stores = _self get "stores";
private _store = _self call ["_read", [_name]];
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found",_name); nil };
_stores set [_name, _data];
SETVAR(profileNamespace,FORGE_STORE_REG,_stores);
saveMissionProfileNamespace;
_store
}],
["_update", {
params [["_name", "", [""]], ["_key", "", [""]], ["_keyField", "", [""]], ["_data", nil, ["", [], 0, true, createHashMap]]];
if (_name isEqualTo "" || _key isEqualTo "" || _keyField isEqualTo "" || isNil "_data") exitWith { ERROR_MSG("Store name, key, key field and, or data cannot be empty"); nil };
private _stores = _self get "stores";
private _store = _self call ["_read", [_name]];
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found",_name); nil };
private _keyData = _store get _key;
if (isNil "_keyData") exitWith { ERROR_MSG_2("Key %1 not found in store %2",_key,_name); nil };
_keyData set [_keyField, _data];
_store set [_key, _keyData];
_stores set [_name, _store];
_store
}],
["_delete", {
params [["_name", "", [""]]];
if (_name isEqualTo "") exitWith { ERROR_MSG("Store name cannot be empty"); false };
private _stores = _self get "stores";
private _store = _self call ["get", [_name]];
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found",_name); false };
_stores deleteAt _name;
true
}],
["set", {
params [["_key", "", [""]], ["_value", nil, [[]]]];
if (_key isEqualTo "" || isNil "_value") exitWith { ERROR_MSG("Key and, or value cannot be empty") };
["set", _key, "", -1, _value] call dragonfly_db_fnc_addTask;
}],
["get", {
params [["_key", "", [""]], ["_function", "", [""]], ["_call", false, [false]], ["_netId", "", [""]]];
if (_key isEqualTo "" || _function isEqualTo "") exitWith { ERROR_MSG("Key and, or function cannot be empty") };
["get", _key, "", -1, [], _function, _call, _netId] call dragonfly_db_fnc_addTask;
}],
["delete", {
params [["_key", "", [""]]];
if (_key isEqualTo "") exitWith { ERROR_MSG("Key cannot be empty") };
["del", _key] call dragonfly_db_fnc_addTask;
}],
["hashGet", {
params [["_keyField", "", [""]], ["_function", "", [""]], ["_call", false, [false]], ["_netId", "", [""]]];
if (_keyField isEqualTo "" || _function isEqualTo "") exitWith { ERROR_MSG("Key field and, or function cannot be empty") };
["hget", "", _keyField, -1, [], _function, _call, _netId] call dragonfly_db_fnc_addTask;
}],
["hashGetId", {
params [["_key", "", [""]], ["_keyField", "", [""]], ["_function", "", [""]], ["_call", false, [false]], ["_netId", "", [""]]];
if (_key isEqualTo "" || _keyField isEqualTo "" || _function isEqualTo "") exitWith { ERROR_MSG("Key, key field and, or function cannot be empty") };
["hgetid", _key, _keyField, -1, [], _function, _call, _netId] call dragonfly_db_fnc_addTask;
}],
["hashGetAll", {
params [["_function", "", [""]], ["_call", false, [false]], ["_netId", "", [""]]];
if (_function isEqualTo "") exitWith { ERROR_MSG("Function cannot be empty") };
["hgetall", "", "", -1, [], _function, _call, _netId] call dragonfly_db_fnc_addTask;
}],
["hashGetAllId", {
params [["_key", "", [""]], ["_function", "", [""]], ["_call", false, [false]], ["_netId", "", [""]]];
if (_key isEqualTo "" || _function isEqualTo "") exitWith { ERROR_MSG("Key and, or function cannot be empty") };
["hgetallid", _key, "", -1, [], _function, _call, _netId] call dragonfly_db_fnc_addTask;
}],
["hashSet", {
params [["_keyField", "", [""]], ["_value", nil, [[]]]];
if (_keyField isEqualTo "" || isNil "_value") exitWith { ERROR_MSG("Key field and, or value cannot be empty") };
["hset", "", _keyField, -1, _value] call dragonfly_db_fnc_addTask;
}],
["hashSetId", {
params [["_key", "", [""]], ["_keyField", "", [""]], ["_value", nil, [[]]]];
if (_key isEqualTo "" || _keyField isEqualTo "" || isNil "_value") exitWith { ERROR_MSG("Key, keyField and, or value cannot be empty") };
["hsetid", _key, _keyField, -1, _value] call dragonfly_db_fnc_addTask;
}],
["hashSetBulk", {
params [["_value", nil, [[]]]];
if (isNil "_value") exitWith { ERROR_MSG("Value cannot be empty") };
["hsetbulk", "", "", -1, _value] call dragonfly_db_fnc_addTask;
}],
["hashSetIdBulk", {
params [["_value", nil, [[]]]];
if (isNil "_value") exitWith { ERROR_MSG("Value cannot be empty") };
["hsetidbulk", "", "", -1, _value] call dragonfly_db_fnc_addTask;
}],
["hashDelete", {
params [["_keyField", "", [""]]];
if (_keyField isEqualTo "") exitWith { ERROR_MSG("Key field cannot be empty") };
["hdel", "", _keyField] call dragonfly_db_fnc_addTask;
}],
["hashDeleteId", {
params [["_key", "", [""]], ["_keyField", "", [""]]];
if (_key isEqualTo "" || _keyField isEqualTo "") exitWith { ERROR_MSG("Key and, or key field cannot be empty") };
["hdelid", _key, _keyField] call dragonfly_db_fnc_addTask;
}],
["listAdd", {
params [["_key", "", [""]], ["_value", nil, [[]]]];
if (_key isEqualTo "" || isNil "_value") exitWith { ERROR_MSG("Key and, or value cannot be empty") };
["listadd", _key, "", -1, _value] call dragonfly_db_fnc_addTask;
}],
["listGet", {
params [["_key", "", [""]], ["_index", -1, [0]], ["_function", "", [""]], ["_call", false, [false]], ["_netId", "", [""]]];
if (_key isEqualTo "" || _index isEqualTo -1 || _function isEqualTo "") exitWith { ERROR_MSG("Key, index and, or function cannot be empty or -1") };
["listidx", _key, "", _index, [], _function, _call, _netId] call dragonfly_db_fnc_addTask;
}],
["listLoad", {
params [["_key", "", [""]], ["_function", "", [""]], ["_call", false, [false]], ["_netId", "", [""]]];
if (_key isEqualTo "" || _function isEqualTo "") exitWith { ERROR_MSG("Key and, or function cannot be empty") };
["listrng", _key, "", -1, [], _function, _call, _netId] call dragonfly_db_fnc_addTask;
}],
["listRemove", {
params [["_key", "", [""]], ["_index", -1, [0]]];
if (_key isEqualTo "" || _index isEqualTo -1) exitWith { ERROR_MSG("Key and, or index cannot be empty or -1") };
["listrem", _key, "", _index] call dragonfly_db_fnc_addTask;
}],
["listSet", {
params [["_key", "", [""]], ["_index", -1, [0]], ["_value", nil, [[]]]];
if (_key isEqualTo "" || _index isEqualTo -1 || isNil "_value") exitWith { ERROR_MSG("Key, index and, or value cannot be empty or -1") };
["listset", _key, "", _index, _value] call dragonfly_db_fnc_addTask;
}]
]];
SETMVAR(FORGE_STORE_REG,_storeInterface);
SETPVAR(missionNamespace,FORGE_STORE_REG,_storeInterface);
GETMVAR(FORGE_STORE_REG,_storeInterface)

View File

@ -0,0 +1,33 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_loadFromMission
* Author: J. Schmidt
*
* Description:
* Loads data from mission profile namespace
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to retrieve <STRING>
* 2: _keyField - Field of the key to retrieve <STRING>
*
* Return Value:
* Retrieved data
*/
params [["_name", "", [""]], ["_key", "", [""]], ["_keyField", "", [""]]];
if (_name isEqualTo "") exitWith { ERROR_MSG("Store name cannot be empty"); nil };
private _store = missionProfileNamespace getVariable _name;
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found",_name); nil };
if (_key isEqualTo "") then { _store } else { _store get _key };
if (_keyField isNotEqualTo "") then {
private _keyData = _store get _key;
if (isNil "_keyData") exitWith { ERROR_MSG_3("KeyField %1 not found in key %2 of store %3",_keyField,_key,_name); nil };
_keyData get _keyField
};

View File

@ -0,0 +1,33 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_loadFromProfile
* Author: J. Schmidt
*
* Description:
* Loads data from profile namespace
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to retrieve <STRING>
* 2: _keyField - Field of the key to retrieve <STRING>
*
* Return Value:
* Retrieved data
*/
params [["_name", "", [""]], ["_key", "", [""]], ["_keyField", "", [""]]];
if (_name isEqualTo "") exitWith { ERROR_MSG("Store name cannot be empty"); nil };
private _store = profileNamespace getVariable _name;
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found",_name); nil };
if (_key isEqualTo "") then { _store } else { _store get _key };
if (_keyField isNotEqualTo "") then {
private _keyData = _store get _key;
if (isNil "_keyData") exitWith { ERROR_MSG_3("KeyField %1 not found in key %2 of store %3",_keyField,_key,_name); nil };
_keyData get _keyField
};

View File

@ -0,0 +1,33 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_loadFromTemp
* Author: J. Schmidt
*
* Description:
* Loads data from temporary mission store (not persisted between sessions)
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to load from <STRING>
* 2: _keyField - Field of the key to load from <STRING>
*
* Return Value:
* Data <STRING, ARRAY, NUMBER, BOOLEAN, HASHMAP>
*/
params [["_name", "", [""]], ["_key", "", [""]], ["_keyField", "", [""]]];
if (_name isEqualTo "") exitWith { ERROR_MSG("Store name cannot be empty"); nil };
private _store = missionNamespace getVariable _name;
if (isNil "_store") exitWith { ERROR_MSG_1("Store %1 not found",_name); nil };
if (_key isEqualTo "") then { _store } else { _store get _key };
if (_keyField isNotEqualTo "") then {
private _keyData = _store get _key;
if (isNil "_keyData") exitWith { ERROR_MSG_3("KeyField %1 not found in key %2 of store %3",_keyField,_key,_name); nil };
_keyData get _keyField
};

View File

@ -0,0 +1,33 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_loadGameState
* Author: J. Schmidt
*
* Description:
* Loads game state from mission or profile namespace and sets appropriate variables
*
* Arguments:
* 0: _nameSpace - Namespace to load from (mission, profile) <STRING> (default: mission)
*
* Return Value:
* Success <BOOL>
*/
params [["_nameSpace", "mission", [""]]];
private _companyState = createHashMap;
if (_nameSpace == "mission") then {
_companyState = ["companyStore", "CompanyState"] call FUNC(loadFromMission);
} else {
_companyState = ["companyStore", "CompanyState"] call FUNC(loadFromProfile);
};
if (!isNil "_companyState") then {
companyFunds = _companyState getOrDefault ["funds", 0];
companyRating = _companyState getOrDefault ["rating", 0];
companyShareholders = _companyState getOrDefault ["operations", []];
};
true

View File

@ -0,0 +1,85 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_loadPlayerState
* Author: J. Schmidt
*
* Description:
* Loads player state from mission or profile namespace and sets appropriate variables
*
* Arguments:
* 0: _player - Player object <OBJECT>
* 1: _nameSpace - Namespace to load from (mission, profile) <STRING> (default: mission)
*
* Return Value:
* Success <BOOL>
*/
params [["_player", objNull, [objNull]], ["_nameSpace", "mission", [""]]];
if (isNull _player) exitWith { ERROR_MSG("Player object cannot be null"); false };
private _playerUID = getPlayerUID _player;
private _playerState = createHashMap;
if (_nameSpace == "mission") then {
_playerState = ["playerStore", _playerUID] call FUNC(loadFromMission);
} else {
_playerState = ["playerStore", _playerUID] call FUNC(loadFromProfile);
};
if (isNil "_playerState") exitWith { ERROR_MSG_1("Player state for %1 not found",_playerUID); false };
private _reputation = _playerState getOrDefault ["reputation", 0];
private _loadout = _playerState getOrDefault ["loadout", []];
private _direction = _playerState getOrDefault ["direction", 0];
private _cash = _playerState getOrDefault ["cash", 0];
private _bank = _playerState getOrDefault ["bank", 0];
private _armory_unlocks = _playerState getOrDefault ["armory_unlocks", [[],[],[],[]]];
private _garage_unlocks = _playerState getOrDefault ["garage_unlocks", [[],[],[],[],[],[]]];
private _locker = _playerState getOrDefault ["locker", []];
private _garage = _playerState getOrDefault ["garage", []];
private _email = _playerState getOrDefault ["email", "unknown@spearnet.mil"];
private _number = _playerState getOrDefault ["number", "unknown"];
private _paygrade = _playerState getOrDefault ["paygrade", "E1"];
private _stance = _playerState getOrDefault ["stance", ""];
private _holster = _playerState getOrDefault ["holster", true];
private _position = _playerState getOrDefault ["position", getPosASL _player];
SETPVAR(_player,Reputation,_reputation);
SETPVAR(_player,Loadout,_loadout);
SETPVAR(_player,Direction,_direction);
SETPVAR(_player,FORGE_Cash,_cash);
SETPVAR(_player,FORGE_Bank,_bank);
SETPVAR(_player,FORGE_Armory_Unlocks,_armory_unlocks);
SETPVAR(_player,FORGE_Garage_Unlocks,_garage_unlocks);
SETPVAR(_player,FORGE_Locker,_locker);
SETPVAR(_player,FORGE_Garage,_garage);
SETPVAR(_player,FORGE_Email,_email);
SETPVAR(_player,FORGE_Phone_Number,_number);
SETPVAR(_player,FORGE_Paygrade,_paygrade);
SETPVAR(_player,Stance,_stance);
SETPVAR(_player,FORGE_Holster_Weapon,_holster);
SETPVAR(_player,Position,_position);
_player playAction _stance;
if (_holster) then {
[player] call AFUNC(weaponselect,putWeaponAway);
};
_player setPosASL _position;
private _pAlt = ((getPosATLVisual _player) select 2);
private _pVelZ = ((velocity _player) select 2);
if (_pAlt > 5 && _pVelZ < 0) then {
_player setVelocity [0, 0, 0];
_player setPosATL [((getPosATLVisual _player) select 0), ((getPosATLVisual _player) select 1), 1];
hint "You logged off mid air. You were moved to a safe position on the ground.";
};
if (needReload _player == 1) then { reload _player };
SETPVAR(_player,value_loadDone,true);
true

View File

@ -0,0 +1,79 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_processDBRequest
* Author: J. Schmidt
*
* Description:
* Processes database requests
*
* Arguments:
* 0: _type - Type of database operation <STRING>
* 1: _name - Name of the store <STRING>
* 2: _key - Key of the store <STRING>
* 3: _keyField - Field of the key to update <STRING>
* 4: _data - Data for the operation <STRING, ARRAY, NUMBER, BOOLEAN, HASHMAP>
*
* Return Value:
* None
*/
params [["_type", "", [""]], ["_name", "", [""]], ["_key", "", [""]], ["_keyField", "", [""]], ["_data", "", ["", [], 0, true, createHashMap]]];
private _store = call FUNC(verifyDB);
switch (_type) do {
case "createStore": {
params ["_name"];
_store call ["_create", [_name]];
};
case "getStore": {
params ["_name"];
_store call ["_read", [_name]];
};
case "saveToMission": {
params ["_name", "_key", "_data"];
[_name, _key, _data] call FUNC(saveToMission);
};
case "saveToProfile": {
params ["_name", "_key", "_data"];
[_name, _key, _data] call FUNC(saveToProfile);
};
case "saveToStore": {
params ["_name", "_data"];
_store call ["_write", [_name, _data]];
};
case "saveToTemp": {
params ["_name", "_key", "_data"];
[_name, _key, _data] call FUNC(saveToTemp);
};
case "getFromMission": {
params ["_name", "_key", "_keyField"];
[_name, _key, _keyField] call FUNC(loadFromMission);
};
case "getFromProfile": {
params ["_name", "_key", "_keyField"];
[_name, _key, _keyField] call FUNC(loadFromProfile);
};
case "getFromStore": {
params ["_name", "_key"];
_store call ["_read", [_name, _key]];
};
case "getFromTemp": {
params ["_name", "_key", "_keyField"];
[_name, _key, _keyField] call FUNC(loadFromTemp);
};
case "updateStore": {
params ["_name", "_key", "_keyField", "_data"];
_store call ["_update", [_name, _key, _keyField, _data]];
};
case "loadGameState": {
[] call FUNC(loadGameState);
};
case "saveGameState": {
[] call FUNC(saveGameState);
};
default {
WARNING_1("Unknown database request type: %1",_type)
};
};

View File

@ -0,0 +1,70 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveGameState
* Author: J. Schmidt
*
* Description:
* Collects and saves the current game state to mission or profile namespace
*
* Arguments:
* 0: _nameSpace - Namespace to save to (mission, profile) <STRING> (default: mission)
*
* Return Value:
* Success <BOOL>
*/
params [["_nameSpace", "mission", [""]]];
if (isNil "companyFunds") then { companyFunds = 0 };
if (isNil "companyRating") then { companyRating = 0 };
if (isNil "companyShareholders") then { companyShareholders = [] };
if (isNil "companyGarageUnlocks") then { companyGarageUnlocks = [] };
private _companyState = createHashMapFromArray [
["key", "CompanyState"],
["funds", companyFunds],
["rating", companyRating],
["operations", companyShareholders]
];
if (_nameSpace == "mission") then {
["companyStore", "CompanyState", _companyState] call FUNC(saveToMission);
} else {
["companyStore", "CompanyState", _companyState] call FUNC(saveToProfile);
};
{
if (alive _x) then {
private _playerState = createHashMapFromArray [
["key", getPlayerUID _x],
["armory_unlocks", GETVAR(_x,FORGE_Armory_Unlocks,_default_armory_unlocks)],
["garage_unlocks", GETVAR(_x,FORGE_Garage_Unlocks,_default_garage_unlocks)],
["locker", GETVAR(_x,FORGE_Locker,[])],
["garage", GETVAR(_x,FORGE_Garage,[])],
["cash", GETVAR(_x,FORGE_Cash,0)],
["bank", GETVAR(_x,FORGE_Bank,0)],
["number", GETVAR(_x,FORGE_Phone_Number,"unknown")],
["email", GETVAR(_x,FORGE_Email,"unknown@spearnet.mil")],
["paygrade", GETVAR(_x,FORGE_Paygrade,"E1")],
["reputation", rating _x],
["loadout", getUnitLoadout _x],
["holster", GETVAR(_x,FORGE_Holster_Weapon,true)],
["position", getPosASLVisual _x],
["direction", getDirVisual _x]
];
if (isNull objectParent _x) then {
_playerState set ["currentWeapon", currentMuzzle _x];
_playerState set ["stance", stance _x];
};
if (_nameSpace == "mission") then {
["playerStore", getPlayerUID _x, _playerState] call FUNC(saveToMission);
} else {
["playerStore", getPlayerUID _x, _playerState] call FUNC(saveToProfile);
};
};
} forEach playableUnits;
true

View File

@ -0,0 +1,41 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveToMission
* Author: J. Schmidt
*
* Description:
* Saves data to mission namespace through the database interface
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to save under <STRING>
* 2: _keyField - Field of the key to update <STRING>
* 3: _data - Data to save <STRING, ARRAY, NUMBER, BOOLEAN, HASHMAP>
*
* Return Value:
* Success <BOOL>
*/
params [["_name", "", [""]], ["_key", "", [""]], ["_keyField", "", [""]], ["_data", nil, ["", [], 0, true, createHashMap]]];
if (_name isEqualTo "" || _key isEqualTo "" || isNil "_data") exitWith { ERROR_MSG("Store name, key and, or data cannot be empty"); false };
private _database = call FUNC(verifyDB);
private _store = _database call ["_read", [_name]];
if (isNil "_store") then { _store = _database call ["_create", [_name, createHashMap]]; };
if (_keyField isEqualTo "") then {
_store set [_key, _data];
} else {
private _keyData = _store get _key;
_keyData set [_keyField, _data];
_store set [_key, _keyData];
};
_database call ["_write", [_name, _store]];
missionNamespace setVariable [_name, _store];
saveMissionProfileNamespace;
true

View File

@ -0,0 +1,41 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveToProfile
* Author: J. Schmidt
*
* Description:
* Saves data to profile namespace through the database interface
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to save under <STRING>
* 2: _keyField - Field of the key to update <STRING>
* 3: _data - Data to save <STRING, ARRAY, NUMBER, BOOLEAN, HASHMAP>
*
* Return Value:
* Success <BOOL>
*/
params [["_name", "", [""]], ["_key", "", [""]], ["_keyField", "", [""]], ["_data", nil, ["", [], 0, true, createHashMap]]];
if (_name isEqualTo "" || _key isEqualTo "" || isNil "_data") exitWith { ERROR_MSG("Store name, key and, or data cannot be empty"); false };
private _database = call FUNC(verifyDB);
private _store = _database call ["_read", [_name]];
if (isNil "_store") then { _store = _database call ["_create", [_name, createHashMap]]; };
if (_keyField isEqualTo "") then {
_store set [_key, _data];
} else {
private _keyData = _store get _key;
_keyData set [_keyField, _data];
_store set [_key, _keyData];
};
_database call ["_write", [_name, _store]];
profileNamespace setVariable [_name, _store];
saveProfileNamespace;
true

View File

@ -0,0 +1,37 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_saveToTemp
* Author: J. Schmidt
*
* Description:
* Saves data to temporary mission store (not persisted between sessions)
*
* Arguments:
* 0: _name - Store name <STRING>
* 1: _key - Key to save under <STRING>
* 2: _keyField - Field of the key to update <STRING>
* 3: _data - Data to save <STRING, ARRAY, NUMBER, BOOLEAN, HASHMAP>
*
* Return Value:
* Success <BOOL>
*/
params [["_name", "", [""]], ["_key", "", [""]], ["_keyField", "", [""]], ["_data", nil, ["", [], 0, true, createHashMap]]];
if (_name isEqualTo "" || _key isEqualTo "" || isNil "_data") exitWith { ERROR_MSG("Store name, key and, or data cannot be empty"); false };
private _store = missionNamespace getVariable [_name, createHashMap];
if (_keyField isEqualTo "") then {
_store set [_key, _data];
} else {
private _keyData = _store get _key;
_keyData set [_keyField, _data];
_store set [_key, _keyData];
};
missionNamespace setVariable [_name, _store];
true

View File

@ -0,0 +1,20 @@
#include "..\script_component.hpp"
/*
* Function: forge_server_db_fnc_verifyDB
* Author: J. Schmidt
*
* Description:
* Verifies the database is initialized and returns the database object
*
* Arguments:
* None
*
* Return Value:
* Database object <HASHMAP>
*/
private _store = GETMVAR(FORGE_STORE_REG,createHashMap);
if (isNil "_store") then { _store = [] call FUNC(initDB) };
_store

View File

@ -0,0 +1,15 @@
#define COMPONENT db
#define COMPONENT_BEAUTIFIED DB
#include "..\main\script_mod.hpp"
// #define DEBUG_MODE_FULL
// #define DISABLE_COMPILE_CACHE
#ifdef DEBUG_ENABLED_DB
#define DEBUG_MODE_FULL
#endif
#ifdef DEBUG_SETTINGS_DB
#define DEBUG_SETTINGS DEBUG_SETTINGS_DB
#endif
#include "..\main\script_macros.hpp"

View File

@ -8,7 +8,7 @@ switch (_condition) do {
"CompanyState",
"funds", [companyFunds],
"rating", [companyRating],
"operations", [companyGenerals],
"operations", [companyShareholders],
"garage_unlocks", [companyGarageUnlocks]
];

View File

@ -3,8 +3,8 @@
_data = _this;
if (_data isEqualTo [""]) then {
companyGenerals = (configFile >> "CfgPatches" >> "forge_server_main" >> "companyGenerals") call BFUNC(getCfgData);
publicVariable "companyGenerals";
companyShareholders = (configFile >> "CfgPatches" >> "forge_server_main" >> "companyShareholders") call BFUNC(getCfgData);
publicVariable "companyShareholders";
companyFunds = "OP_BUDGET" call BFUNC(getParamValue);
publicVariable "companyFunds";
@ -12,9 +12,6 @@ if (_data isEqualTo [""]) then {
companyRating = "OP_RATING" call BFUNC(getParamValue);
publicVariable "companyRating";
companyGarageUnlocks = [[],[],[],[],[],[]];
publicVariable "companyGarageUnlocks";
diag_log "No Server Entry Found!";
["save"] call FUNC(handleServerState);
@ -32,8 +29,7 @@ if (_data isEqualTo [""]) then {
switch (_key) do {
case "funds": { companyFunds = _value; publicVariable "companyFunds" };
case "rating": { companyRating = _value; publicVariable "companyRating" };
case "operations": { companyGenerals = _value; publicVariable "companyGenerals" };
case "garage_unlocks": { companyGarageUnlocks = _value; publicVariable "companyGarageUnlocks" };
case "operations": { companyShareholders = _value; publicVariable "companyShareholders" };
};
};

View File

@ -3,7 +3,7 @@ class CfgMods {
dir = "@forge_server";
name = "FORGE Server";
author = "IDSolutions";
picture = "A3\Ui_f\data\Logos\arma3_expansion_alpha_ca";
picture = "A3\Ui_f\data\Logos\arma3_expansion_alpha_ca.paa";
hideName = "false";
hidePicture = "false";
action = "https://innovativedevsolutions.org";

View File

@ -13,7 +13,7 @@ class CfgPatches {
serverSaveLoop = 0;
serverSaveLoopTime = 30;
companyGenerals[] = {"76561198027566824"};
companyShareholders[] = {"76561198027566824"};
AISkill = 0.8;
};
};

View File

@ -1,4 +1,4 @@
#define MAJOR 1
#define MINOR 0
#define PATCH 0
#define BUILD 3
#define BUILD 11

View File

@ -1,5 +1,7 @@
PREP(cargoToPairs);
PREP(getPlayer);
PREP(playSound);
PREP(playerGroup2Server);
PREP(redirectClient2Server);
PREP(emailMessage);
PREP(textMessage);

View File

@ -0,0 +1,27 @@
#include "..\script_component.hpp"
/*
* Author: IDSolutions
* Gets a player object by UID.
*
* Arguments:
* 0: Player UID <STRING>
*
* Return Value:
* Player object or objNull if not found <OBJECT>
*
* Example:
* ["76561198012345678"] call forge_crate_common_fnc_getPlayer
*
* Public: Yes
*/
params ["_uid"];
private _player = objNull;
{
if ((getPlayerUID _x) isEqualTo _uid) exitWith { _player = _x; };
} forEach allPlayers;
_player

View File

@ -0,0 +1,10 @@
#include "..\script_component.hpp"
params [["_ip", "127.0.0.1", [""]], ["_port", "2312", [""]], ["_password", "abc", [""]]];
private _units = units group player select { isPlayer _x };
private _machineIDs = _units apply { owner _x };
{
[_ip, _port, _password] remoteExecCall ["forge_server_misc_fnc_redirectClient2Server", _x];
} forEach _machineIDs;

View File

@ -27,38 +27,22 @@ addMissionEventHandler ["HandleDisconnect", {
private _data = [
_uid,
// "armory_unlocks", [_unit getVariable ["Armory_Unlocks", _default_armory_unlocks]],
// "garage_unlocks", [_unit getVariable ["Garage_Unlocks", _default_garage_unlocks]],
"armory_unlocks", [GETVAR(_unit,Armory_Unlocks,_default_armory_unlocks)],
"garage_unlocks", [GETVAR(_unit,Garage_Unlocks,_default_garage_unlocks)],
// "locker", [_unit getVariable ["FORGE_Locker", []]],
// "garage", [_unit getVariable ["FORGE_Garage", []]],
"locker", [GETVAR(_unit,FORGE_Locker,[])],
"garage", [GETVAR(_unit,FORGE_Garage,[])],
// "cash", [_unit getVariable ["FORGE_Cash", 0]],
// "bank", [_unit getVariable ["FORGE_Bank", 0]],
"cash", [GETVAR(_unit,FORGE_Cash,0)],
"bank", [GETVAR(_unit,FORGE_Bank,0)],
// "number", [_unit getVariable ["FORGE_Phone_Number", "unknown"]],
// "email", [_unit getVariable ["FORGE_Email", "unknown@spearnet.mil"]],
"number", [GETVAR(_unit,FORGE_Phone_Number,"unknown")],
"email", [GETVAR(_unit,FORGE_Email,"unknown@spearnet.mil")],
// "paygrade", [_unit getVariable ["Paygrade", "E1"]],
"paygrade", [GETVAR(_unit,Paygrade,"E1")],
"number", [GETVAR(_unit,FORGE_Phone_Number,QUOTE(unknown))],
"email", [GETVAR(_unit,FORGE_Email,QUOTE(unknown@spearnet.mil))],
"paygrade", [GETVAR(_unit,FORGE_PayGrade,QUOTE(E1))],
"reputation", [rating _unit],
"loadout", [getUnitLoadout _unit],
// "holster", [_unit getVariable ["FORGE_Holster_Weapon", true]],
"holster", [GETVAR(_unit,FORGE_Holster_Weapon,true)],
"position", [getPosASLVisual _unit],
"direction", [getDirVisual _unit]
];
// if (vehicle _unit == _unit) then {
// _data pushBack "currentWeapon";
// _data pushBack [currentMuzzle _unit];
// _data pushBack "stance";
// _data pushBack [stance _unit];
// };
if (isNull objectParent _unit) then {
_data pushBack "currentWeapon";
_data pushBack [currentMuzzle _unit];

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,7 @@
dir = "@forge_server";
name = "FORGE Server";
author = "IDSolutions";
picture = "title_co.paa";
picture = "title_ca.paa";
logoSmall = "icon_64_ca.paa";
logo = "icon_128_ca.paa";
logoOver = "icon_128_highlight_ca.paa";
@ -9,4 +10,5 @@ tooltipOwned = "IDS Owned";
overview = "FORGE Server - Official Modification";
description = "FORGE Server - Version 1.0.0";
action = "https://innovativedevsolutions.org";
actionName = "Website";
actionName = "Website";
dlcColor[] = {0.45, 0.47, 0.41, 1};

BIN
title_ca.paa Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,187 +0,0 @@
#!/usr/bin/env python3
import fnmatch
import os
import re
import sys
def valid_keyword_after_code(content, index):
for word in ["for", "do", "count", "each", "forEach", "else", "and", "not", "isEqualTo", "isNotEqualTo", "in", "call", "spawn", "execVM", "catch", "param", "select", "apply", "findIf", "remoteExec"]:
if content.find(word, index, index + len(word)) != -1:
return True
return False
def check_sqf(filepath):
errors = []
with open(filepath, "r", encoding = "utf-8", errors = "ignore") as file:
content = file.read()
# Store all brackets we find in this file, so we can validate everything on the end
brackets = []
# Used in case we are in a line comment (//)
ignore_till_eol = False
# To check if we are in a comment block
in_comment_block = False
check_if_comment = False
# Used in case we are in a comment block (/* */)
# This is true if we detect a * inside a comment block
# If the next character is a /, it means we end our comment block
check_if_closing = False
# We ignore everything inside a string
in_string = False
# Used to store the starting type of a string, so we can match that to the end of a string
string_type = ""
# Used to check for semicolon after code blocks
last_is_curly_brace = False
check_for_semicolon = False
# Extra information so we know what line we find errors at
line_number = 1
char_index = 0
for c in content:
if last_is_curly_brace:
last_is_curly_brace = False
# Test generates false positives with binary commands that take CODE as 2nd arg (e.g. findIf)
check_for_semicolon = not re.search("findIf", content, re.IGNORECASE)
# Keep track of current line number
if c == "\n":
line_number += 1
# While we are in a string, we can ignore everything else, except the end of the string
if in_string:
if c == string_type:
in_string = False
# Look for the end of this comment block
elif in_comment_block:
if c == "*":
check_if_closing = True
elif check_if_closing:
if c == "/":
in_comment_block = False
elif c != "*":
check_if_closing = False
# If we are not in a comment block, we will check if we are at the start of one or count the () {} and []
else:
# This means we have encountered a /, so we are now checking if this is an inline comment or a comment block
if check_if_comment:
check_if_comment = False
# If the next character after / is a *, we are at the start of a comment block
if c == "*":
in_comment_block = True
# Otherwise, check if we are in an line comment, / followed by another / (//)
elif c == "/":
ignore_till_eol = True
if not in_comment_block:
if ignore_till_eol:
# We are in a line comment, just continue going through the characters until we find an end of line
if c == "\n":
ignore_till_eol = False
else:
if c == '"' or c == "'":
in_string = True
string_type = c
elif c == "/":
check_if_comment = True
elif c == "\t":
errors.append(" ERROR: Found a tab on line {}.".format(line_number))
elif c in ["(", "[", "{"]:
brackets.append(c)
elif c == ")":
if not brackets or brackets[-1] in ["[", "{"]:
errors.append(" ERROR: Missing parenthesis '(' on line {}.".format(line_number))
brackets.append(c)
elif c == "]":
if not brackets or brackets[-1] in ["(", "{"]:
errors.append(" ERROR: Missing square bracket '[' on line {}.".format(line_number))
brackets.append(c)
elif c == "}":
last_is_curly_brace = True
if not brackets or brackets[-1] in ["(", "["]:
errors.append(" ERROR: Missing curly brace '{{' on line {}.".format(line_number))
brackets.append(c)
if check_for_semicolon:
# Keep reading until no white space or comments
if c not in [" ", "\t", "\n", "/"]:
check_for_semicolon = False
if c not in ["]", ")", "}", ";", ",", "&", "!", "|", "="] and not valid_keyword_after_code(content, char_index):
errors.append(" ERROR: Possible missing semicolon ';' on line {}.".format(line_number))
char_index += 1
# Compare opening and closing bracket counts
if brackets.count("(") != brackets.count(")"):
errors.append(" ERROR: Unequal number of parentheses, '(' = {}, ')' = {}.".format(brackets.count("("), brackets.count(")")))
if brackets.count("[") != brackets.count("]"):
errors.append(" ERROR: Unequal number of square brackets, '[' = {}, ']' = {}.".format(brackets.count("["), brackets.count("]")))
if brackets.count("{") != brackets.count("}"):
errors.append(" ERROR: Unequal number of curly braces, '{{' = {}, '}}' = {}.".format(brackets.count("{"), brackets.count("}")))
# Ensure includes are before block comments
if re.compile('\s*(/\*[\s\S]+?\*/)\s*#include').match(content):
errors.append(" ERROR: Found an #include after a block comment.")
return errors
def main():
print("Validating SQF")
print("--------------")
# Allow running from root directory and tools directory
root_dir = ".."
if os.path.exists("addons"):
root_dir = "."
# Check all SQF files in the project directory
sqf_files = []
for root, _, files in os.walk(root_dir):
for file in fnmatch.filter(files, "*.sqf"):
sqf_files.append(os.path.join(root, file))
sqf_files.sort()
bad_count = 0
for filepath in sqf_files:
errors = check_sqf(filepath)
if errors:
print("\nFound {} error(s) in {}:".format(len(errors), os.path.relpath(filepath, root_dir)))
for error in errors:
print(error)
bad_count += 1
print("\nChecked {} files, found errors in {}.".format(len(sqf_files), bad_count))
if bad_count == 0:
print("SQF Validation PASSED")
else:
print("SQF Validation FAILED")
return bad_count
if __name__ == "__main__":
sys.exit(main())