diff --git a/.gitignore b/.gitignore index 5465a761..7974ca94 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ releases/ # VSCode Extension node_modules -dist/ *.vsix # Browser Extensions diff --git a/Cargo.lock b/Cargo.lock index 4dada3ed..3bbb0452 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,6 +181,34 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "arma-rs" +version = "1.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e722ed20b6266ceccf4334d32a528fcef9a2dca341b8bbcceb99eb7152081239" +dependencies = [ + "arma-rs-proc", + "crossbeam-channel", + "libc", + "link_args", + "log", + "seq-macro", + "state", + "winapi", + "windows 0.58.0", +] + +[[package]] +name = "arma-rs-proc" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936ed035ff4e775bd50ff94ccdb44f236d1ca012c376347b048fb6b9861833b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "arma3-wiki" version = "0.3.3" @@ -947,6 +975,12 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "either" version = "1.13.0" @@ -1227,6 +1261,19 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.48.0", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1435,17 +1482,21 @@ dependencies = [ "hemtt-sqf", "hemtt-stringtable", "hemtt-workspace", + "image", "indicatif", + "interprocess", "num_cpus", "paste", "rayon", "regex", "reqwest", "rhai", + "rust-embed", "sealed_test", "semver", "serde", "serde_json", + "state", "supports-hyperlinks", "tabled", "terminal-link", @@ -1476,6 +1527,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "hemtt-comm" +version = "0.1.0" +dependencies = [ + "arma-rs", + "hemtt-common", + "interprocess", + "serde_json", +] + [[package]] name = "hemtt-common" version = "1.0.0" @@ -1581,6 +1642,10 @@ dependencies = [ "thiserror 2.0.3", ] +[[package]] +name = "hemtt-photoshoot" +version = "1.0.0" + [[package]] name = "hemtt-preprocessor" version = "1.0.0" @@ -1897,7 +1962,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -2187,6 +2252,19 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "interprocess" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb" +dependencies = [ + "doctest-file", + "libc", + "recvmsg", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -2420,6 +2498,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link_args" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7721e472624c9aaad27a5eb6b7c9c6045c7a396f2efb6dabaec1b640d5e89b" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -2480,6 +2564,21 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + [[package]] name = "loop9" version = "0.1.5" @@ -3527,6 +3626,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.5.7" @@ -3912,6 +4017,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +[[package]] +name = "seq-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" + [[package]] name = "serde" version = "1.0.215" @@ -4169,6 +4280,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "state" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" +dependencies = [ + "loom", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -5180,6 +5300,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -5211,6 +5337,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -5220,6 +5365,41 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "windows-registry" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 71fe3455..08b7372d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,14 @@ members = [ "libs/p3d", "libs/paa", "libs/pbo", + "libs/photoshoot", "libs/preprocessor", "libs/signing", "libs/sqf", "libs/stringtable", "libs/workspace", + + "arma", ] resolver = "2" @@ -45,6 +48,7 @@ dirs = "5.0.1" git2 = "0.19.0" indexmap = "2.6.0" insta = "1.41.1" +interprocess = "2.2.2" linkme = "0.3.31" lsp-types = "0.97.0" paste = "1.0.15" diff --git a/arma/.hemtt/project.toml b/arma/.hemtt/project.toml new file mode 100644 index 00000000..bb5fe0fc --- /dev/null +++ b/arma/.hemtt/project.toml @@ -0,0 +1,15 @@ +name = "HEMTT" +author = "BrettMayson" +prefix = "hemtt" +mainprefix = "x" + +[version] +git_hash = 0 +major = 0 +minor = 0 +patch = 0 + +[files] +include = [ + "hemtt_comm_x64.dll" +] diff --git a/arma/Cargo.toml b/arma/Cargo.toml new file mode 100644 index 00000000..b97fbd85 --- /dev/null +++ b/arma/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hemtt-comm" +authors = ["Brett Mayson "] +version = "0.1.0" +edition = "2021" + +[lib] +name = "hemtt_comm" +crate-type = ["cdylib"] + +[dependencies] +hemtt-common = { path = "../libs/common" } + +arma-rs = "1.10.5" +interprocess = { workspace = true } +serde_json = { workspace = true } diff --git a/arma/addons/main/$PBOPREFIX$ b/arma/addons/main/$PBOPREFIX$ new file mode 100644 index 00000000..299bb391 --- /dev/null +++ b/arma/addons/main/$PBOPREFIX$ @@ -0,0 +1 @@ +x\hemtt\addons\main diff --git a/arma/addons/main/CfgFunctions.hpp b/arma/addons/main/CfgFunctions.hpp new file mode 100644 index 00000000..b37efd33 --- /dev/null +++ b/arma/addons/main/CfgFunctions.hpp @@ -0,0 +1,10 @@ +class CfgFunctions { + class HEMTT { + class Extension { + class setMission { + file = QPATHTO_F(functions\setMission); + preInit = 1; + }; + }; + }; +}; diff --git a/arma/addons/main/config.cpp b/arma/addons/main/config.cpp new file mode 100644 index 00000000..aa9f058e --- /dev/null +++ b/arma/addons/main/config.cpp @@ -0,0 +1,14 @@ +#include "script_component.hpp" + +class CfgPatches { + class ADDON { + name = QUOTE(COMPONENT); + units[] = {}; + weapons[] = {}; + requiredVersion = REQUIRED_VERSION; + requiredAddons[] = {}; + VERSION_CONFIG; + }; +}; + +#include "CfgFunctions.hpp" diff --git a/arma/addons/main/functions/setMission.sqf b/arma/addons/main/functions/setMission.sqf new file mode 100644 index 00000000..8daf3cb4 --- /dev/null +++ b/arma/addons/main/functions/setMission.sqf @@ -0,0 +1,2 @@ +diag_log format ["setMission: %1", getMissionPath ""]; +diag_log format ["response: %1", "hemtt_comm" callExtension ["mission", [getMissionPath ""]]]; diff --git a/arma/addons/main/script_component.hpp b/arma/addons/main/script_component.hpp new file mode 100644 index 00000000..003c4c6b --- /dev/null +++ b/arma/addons/main/script_component.hpp @@ -0,0 +1,4 @@ +#define COMPONENT main + +#include "script_mod.hpp" +#include "script_macros.hpp" diff --git a/arma/addons/main/script_macros.hpp b/arma/addons/main/script_macros.hpp new file mode 100644 index 00000000..31201c26 --- /dev/null +++ b/arma/addons/main/script_macros.hpp @@ -0,0 +1,3 @@ +#include "script_macros_common.hpp" + +#define DFUNC(var1) TRIPLES(ADDON,fnc,var1) diff --git a/arma/addons/main/script_macros_common.hpp b/arma/addons/main/script_macros_common.hpp new file mode 100644 index 00000000..add43ea1 --- /dev/null +++ b/arma/addons/main/script_macros_common.hpp @@ -0,0 +1,1763 @@ +/* + Header: script_macros_common.hpp + + Description: + A general set of useful macro functions for use by CBA itself or by any module that uses CBA. + + Authors: + Sickboy and Spooner +*/ + +/* **************************************************** + New - Should be exported to general addon + Aim: + - Simplify (shorten) the amount of characters required for repetitive tasks + - Provide a solid structure that can be dynamic and easy editable (Which sometimes means we cannot adhere to Aim #1 ;-) + An example is the path that is built from defines. Some available in this file, others in mods and addons. + + Follows Standard: + Object variables: PREFIX_COMPONENT + Main-object variables: PREFIX_main + Paths: MAINPREFIX\PREFIX\SUBPREFIX\COMPONENT\SCRIPTNAME.sqf + e.g: x\six\addons\sys_menu\fDate.sqf + + Usage: + define PREFIX and COMPONENT, then include this file + (Note, you could have a main addon for your mod, define the PREFIX in a macros.hpp, + and include this script_macros_common.hpp file. + Then in your addons, add a component.hpp, define the COMPONENT, + and include your mod's script_macros.hpp + In your scripts you can then include the addon's component.hpp with relative path) + + TODO: + - Try only to use 1 string type " vs ' + - Evaluate double functions, and simplification + - Evaluate naming scheme; current = prototype + - Evaluate "Debug" features.. + - Evaluate "create mini function per precompiled script, that will load the script on first usage, rather than on init" + - Also saw "Namespace" typeName, evaluate which we need :P + - Single/Multi player gamelogics? (Incase of MP, you would want only 1 gamelogic per component, which is pv'ed from server, etc) + */ + +#ifndef MAINPREFIX + #define MAINPREFIX x +#endif + +#ifndef SUBPREFIX + #define SUBPREFIX addons +#endif + +#ifndef MAINLOGIC + #define MAINLOGIC main +#endif + +#ifndef VERSION + #define VERSION 0 +#endif + +#ifndef VERSION_AR + #define VERSION_AR VERSION +#endif + +#ifndef VERSION_CONFIG + #define VERSION_CONFIG version = QUOTE(VERSION); versionStr = QUOTE(VERSION); versionAr[] = {VERSION_AR} +#endif + +#define ADDON DOUBLES(PREFIX,COMPONENT) +#define MAIN_ADDON DOUBLES(PREFIX,main) + +/* ------------------------------------------- +Group: Debugging +------------------------------------------- */ + +/* ------------------------------------------- +Macros: DEBUG_MODE_x + Managing debugging based on debug level. + + According to the *highest* level of debugging that has been defined *before* script_macros_common.hpp is included, + only the appropriate debugging commands will be functional. With no level explicitely defined, assume DEBUG_MODE_NORMAL. + + DEBUG_MODE_FULL - Full debugging output. + DEBUG_MODE_NORMAL - All debugging except and (Default setting if none specified). + DEBUG_MODE_MINIMAL - Only and enabled. + +Examples: + In order to turn on full debugging for a single file, + (begin example) + // Top of individual script file. + #define DEBUG_MODE_FULL + #include "script_component.hpp" + (end) + + In order to force minimal debugging for a single component, + (begin example) + // Top of addons\\script_component.hpp + // Ensure that any FULL and NORMAL setting from the individual files are undefined and MINIMAL is set. + #ifdef DEBUG_MODE_FULL + #undef DEBUG_MODE_FULL + #endif + #ifdef DEBUG_MODE_NORMAL + #undef DEBUG_MODE_NORMAL + #endif + #ifndef DEBUG_MODE_MINIMAL + #define DEBUG_MODE_MINIMAL + #endif + #include "script_macros.hpp" + (end) + + In order to turn on full debugging for a whole addon, + (begin example) + // Top of addons\main\script_macros.hpp + #ifndef DEBUG_MODE_FULL + #define DEBUG_MODE_FULL + #endif + #include "\x\cba\addons\main\script_macros_common.hpp" + (end) + +Author: + Spooner +------------------------------------------- */ + +// If DEBUG_MODE_FULL, then also enable DEBUG_MODE_NORMAL. +#ifdef DEBUG_MODE_FULL +#define DEBUG_MODE_NORMAL +#endif + +// If DEBUG_MODE_NORMAL, then also enable DEBUG_MODE_MINIMAL. +#ifdef DEBUG_MODE_NORMAL +#define DEBUG_MODE_MINIMAL +#endif + +// If no debug modes specified, use DEBUG_MODE_NORMAL (+ DEBUG_MODE_MINIMAL). +#ifndef DEBUG_MODE_MINIMAL +#define DEBUG_MODE_NORMAL +#define DEBUG_MODE_MINIMAL +#endif + +#define LOG_SYS_FORMAT(LEVEL,MESSAGE) format ['[%1] (%2) %3: %4', toUpper 'PREFIX', 'COMPONENT', LEVEL, MESSAGE] + +#ifdef DEBUG_SYNCHRONOUS +#define LOG_SYS(LEVEL,MESSAGE) diag_log text LOG_SYS_FORMAT(LEVEL,MESSAGE) +#else +#define LOG_SYS(LEVEL,MESSAGE) LOG_SYS_FORMAT(LEVEL,MESSAGE) call CBA_fnc_log +#endif + +#define LOG_SYS_FILELINENUMBERS(LEVEL,MESSAGE) LOG_SYS(LEVEL,format [ARR_4('%1 %2:%3',MESSAGE,__FILE__,__LINE__ + 1)]) + +/* ------------------------------------------- +Macro: LOG() + Log a debug message into the RPT log. + + Only run if is defined. + +Parameters: + MESSAGE - Message to record + +Example: + (begin example) + LOG("Initiated clog-dancing simulator."); + (end) + +Author: + Spooner +------------------------------------------- */ +#ifdef DEBUG_MODE_FULL + +#define LOG(MESSAGE) LOG_SYS_FILELINENUMBERS('LOG',MESSAGE) +#define LOG_1(MESSAGE,ARG1) LOG(FORMAT_1(MESSAGE,ARG1)) +#define LOG_2(MESSAGE,ARG1,ARG2) LOG(FORMAT_2(MESSAGE,ARG1,ARG2)) +#define LOG_3(MESSAGE,ARG1,ARG2,ARG3) LOG(FORMAT_3(MESSAGE,ARG1,ARG2,ARG3)) +#define LOG_4(MESSAGE,ARG1,ARG2,ARG3,ARG4) LOG(FORMAT_4(MESSAGE,ARG1,ARG2,ARG3,ARG4)) +#define LOG_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5) LOG(FORMAT_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5)) +#define LOG_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) LOG(FORMAT_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6)) +#define LOG_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) LOG(FORMAT_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7)) +#define LOG_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) LOG(FORMAT_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8)) + +#else + +#define LOG(MESSAGE) /* disabled */ +#define LOG_1(MESSAGE,ARG1) /* disabled */ +#define LOG_2(MESSAGE,ARG1,ARG2) /* disabled */ +#define LOG_3(MESSAGE,ARG1,ARG2,ARG3) /* disabled */ +#define LOG_4(MESSAGE,ARG1,ARG2,ARG3,ARG4) /* disabled */ +#define LOG_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5) /* disabled */ +#define LOG_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) /* disabled */ +#define LOG_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) /* disabled */ +#define LOG_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) /* disabled */ + +#endif + +/* ------------------------------------------- +Macro: INFO() + Record a message without file and line number in the RPT log. + +Parameters: + MESSAGE - Message to record + +Example: + (begin example) + INFO("Mod X is loaded, do Y"); + (end) + +Author: + commy2 +------------------------------------------- */ +#define INFO(MESSAGE) LOG_SYS('INFO',MESSAGE) +#define INFO_1(MESSAGE,ARG1) INFO(FORMAT_1(MESSAGE,ARG1)) +#define INFO_2(MESSAGE,ARG1,ARG2) INFO(FORMAT_2(MESSAGE,ARG1,ARG2)) +#define INFO_3(MESSAGE,ARG1,ARG2,ARG3) INFO(FORMAT_3(MESSAGE,ARG1,ARG2,ARG3)) +#define INFO_4(MESSAGE,ARG1,ARG2,ARG3,ARG4) INFO(FORMAT_4(MESSAGE,ARG1,ARG2,ARG3,ARG4)) +#define INFO_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5) INFO(FORMAT_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5)) +#define INFO_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) INFO(FORMAT_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6)) +#define INFO_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) INFO(FORMAT_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7)) +#define INFO_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) INFO(FORMAT_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8)) + +/* ------------------------------------------- +Macro: WARNING() + Record a non-critical error in the RPT log. + + Only run if or higher is defined. + +Parameters: + MESSAGE - Message to record + +Example: + (begin example) + WARNING("This function has been deprecated. Please don't use it in future!"); + (end) + +Author: + Spooner +------------------------------------------- */ +#ifdef DEBUG_MODE_NORMAL + +#define WARNING(MESSAGE) LOG_SYS_FILELINENUMBERS('WARNING',MESSAGE) +#define WARNING_1(MESSAGE,ARG1) WARNING(FORMAT_1(MESSAGE,ARG1)) +#define WARNING_2(MESSAGE,ARG1,ARG2) WARNING(FORMAT_2(MESSAGE,ARG1,ARG2)) +#define WARNING_3(MESSAGE,ARG1,ARG2,ARG3) WARNING(FORMAT_3(MESSAGE,ARG1,ARG2,ARG3)) +#define WARNING_4(MESSAGE,ARG1,ARG2,ARG3,ARG4) WARNING(FORMAT_4(MESSAGE,ARG1,ARG2,ARG3,ARG4)) +#define WARNING_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5) WARNING(FORMAT_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5)) +#define WARNING_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) WARNING(FORMAT_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6)) +#define WARNING_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) WARNING(FORMAT_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7)) +#define WARNING_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) WARNING(FORMAT_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8)) + +#else + +#define WARNING(MESSAGE) /* disabled */ +#define WARNING_1(MESSAGE,ARG1) /* disabled */ +#define WARNING_2(MESSAGE,ARG1,ARG2) /* disabled */ +#define WARNING_3(MESSAGE,ARG1,ARG2,ARG3) /* disabled */ +#define WARNING_4(MESSAGE,ARG1,ARG2,ARG3,ARG4) /* disabled */ +#define WARNING_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5) /* disabled */ +#define WARNING_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) /* disabled */ +#define WARNING_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) /* disabled */ +#define WARNING_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) /* disabled */ + +#endif + +/* ------------------------------------------- +Macro: ERROR() + Record a critical error in the RPT log. + +Parameters: + MESSAGE - Message to record + +Example: + (begin example) + ERROR("value of frog not found in config ...yada...yada..."); + (end) + +Author: + Spooner +------------------------------------------- */ +#define ERROR(MESSAGE) LOG_SYS_FILELINENUMBERS('ERROR',MESSAGE) +#define ERROR_1(MESSAGE,ARG1) ERROR(FORMAT_1(MESSAGE,ARG1)) +#define ERROR_2(MESSAGE,ARG1,ARG2) ERROR(FORMAT_2(MESSAGE,ARG1,ARG2)) +#define ERROR_3(MESSAGE,ARG1,ARG2,ARG3) ERROR(FORMAT_3(MESSAGE,ARG1,ARG2,ARG3)) +#define ERROR_4(MESSAGE,ARG1,ARG2,ARG3,ARG4) ERROR(FORMAT_4(MESSAGE,ARG1,ARG2,ARG3,ARG4)) +#define ERROR_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5) ERROR(FORMAT_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5)) +#define ERROR_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) ERROR(FORMAT_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6)) +#define ERROR_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) ERROR(FORMAT_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7)) +#define ERROR_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) ERROR(FORMAT_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8)) + +/* ------------------------------------------- +Macro: ERROR_MSG() + Record a critical error in the RPT log and display on screen error message. + + Newlines (\n) in the MESSAGE will be put on separate lines. + +Parameters: + MESSAGE - Message to record + +Example: + (begin example) + ERROR_MSG("value of frog not found in config ...yada...yada..."); + (end) + +Author: + commy2 +------------------------------------------- */ +#define ERROR_MSG(MESSAGE) ['PREFIX', 'COMPONENT', nil, MESSAGE, __FILE__, __LINE__ + 1] call CBA_fnc_error +#define ERROR_MSG_1(MESSAGE,ARG1) ERROR_MSG(FORMAT_1(MESSAGE,ARG1)) +#define ERROR_MSG_2(MESSAGE,ARG1,ARG2) ERROR_MSG(FORMAT_2(MESSAGE,ARG1,ARG2)) +#define ERROR_MSG_3(MESSAGE,ARG1,ARG2,ARG3) ERROR_MSG(FORMAT_3(MESSAGE,ARG1,ARG2,ARG3)) +#define ERROR_MSG_4(MESSAGE,ARG1,ARG2,ARG3,ARG4) ERROR_MSG(FORMAT_4(MESSAGE,ARG1,ARG2,ARG3,ARG4)) +#define ERROR_MSG_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5) ERROR_MSG(FORMAT_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5)) +#define ERROR_MSG_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) ERROR_MSG(FORMAT_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6)) +#define ERROR_MSG_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) ERROR_MSG(FORMAT_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7)) +#define ERROR_MSG_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) ERROR_MSG(FORMAT_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8)) + +/* ------------------------------------------- +Macro: ERROR_WITH_TITLE() + Record a critical error in the RPT log. + + The title can be specified (in the heading is always just "ERROR") + Newlines (\n) in the MESSAGE will be put on separate lines. + +Parameters: + TITLE - Title of error message + MESSAGE - Body of error message + +Example: + (begin example) + ERROR_WITH_TITLE("Value not found","Value of frog not found in config ...yada...yada..."); + (end) + +Author: + Spooner +------------------------------------------- */ +#define ERROR_WITH_TITLE(TITLE,MESSAGE) ['PREFIX', 'COMPONENT', TITLE, MESSAGE, __FILE__, __LINE__ + 1] call CBA_fnc_error +#define ERROR_WITH_TITLE_1(TITLE,MESSAGE,ARG1) ERROR_WITH_TITLE(TITLE,FORMAT_1(MESSAGE,ARG1)) +#define ERROR_WITH_TITLE_2(TITLE,MESSAGE,ARG1,ARG2) ERROR_WITH_TITLE(TITLE,FORMAT_2(MESSAGE,ARG1,ARG2)) +#define ERROR_WITH_TITLE_3(TITLE,MESSAGE,ARG1,ARG2,ARG3) ERROR_WITH_TITLE(TITLE,FORMAT_3(MESSAGE,ARG1,ARG2,ARG3)) +#define ERROR_WITH_TITLE_4(TITLE,MESSAGE,ARG1,ARG2,ARG3,ARG4) ERROR_WITH_TITLE(TITLE,FORMAT_4(MESSAGE,ARG1,ARG2,ARG3,ARG4)) +#define ERROR_WITH_TITLE_5(TITLE,MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5) ERROR_WITH_TITLE(TITLE,FORMAT_5(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5)) +#define ERROR_WITH_TITLE_6(TITLE,MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) ERROR_WITH_TITLE(TITLE,FORMAT_6(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6)) +#define ERROR_WITH_TITLE_7(TITLE,MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) ERROR_WITH_TITLE(TITLE,FORMAT_7(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7)) +#define ERROR_WITH_TITLE_8(TITLE,MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) ERROR_WITH_TITLE(TITLE,FORMAT_8(MESSAGE,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8)) + +/* ------------------------------------------- +Macro: MESSAGE_WITH_TITLE() + Record a single line in the RPT log. + +Parameters: + TITLE - Title of log message + MESSAGE - Body of message + +Example: + (begin example) + MESSAGE_WITH_TITLE("Value found","Value of frog found in config "); + (end) + +Author: + Killswitch +------------------------------------------- */ +#define MESSAGE_WITH_TITLE(TITLE,MESSAGE) LOG_SYS_FILELINENUMBERS(TITLE,MESSAGE) + +/* ------------------------------------------- +Macro: RETDEF() + If a variable is undefined, return the default value. Otherwise, return the + variable itself. + +Parameters: + VARIABLE - the variable to check + DEFAULT_VALUE - the default value to use if variable is undefined + +Example: + (begin example) + // _var is undefined + hintSilent format ["_var=%1", RETDEF(_var,5)]; // "_var=5" + _var = 7; + hintSilent format ["_var=%1", RETDEF(_var,5)]; // "_var=7" + (end example) +Author: + 654wak654 +------------------------------------------- */ +#define RETDEF(VARIABLE,DEFAULT_VALUE) (if (isNil {VARIABLE}) then [{DEFAULT_VALUE}, {VARIABLE}]) + +/* ------------------------------------------- +Macro: RETNIL() + If a variable is undefined, return the value nil. Otherwise, return the + variable itself. + +Parameters: + VARIABLE - the variable to check + +Example: + (begin example) + // _var is undefined + hintSilent format ["_var=%1", RETNIL(_var)]; // "_var=any" + (end example) + +Author: + Alef (see CBA issue #8514) +------------------------------------------- */ +#define RETNIL(VARIABLE) RETDEF(VARIABLE,nil) + +/* ------------------------------------------- +Macros: TRACE_n() + Log a message and 1-8 variables to the RPT log. + + Only run if is defined. + + TRACE_1(MESSAGE,A) - Log 1 variable. + TRACE_2(MESSAGE,A,B) - Log 2 variables. + TRACE_3(MESSAGE,A,B,C) - Log 3 variables. + TRACE_4(MESSAGE,A,B,C,D) - Log 4 variables. + TRACE_5(MESSAGE,A,B,C,D,E) - Log 5 variables. + TRACE_6(MESSAGE,A,B,C,D,E,F) - Log 6 variables. + TRACE_7(MESSAGE,A,B,C,D,E,F,G) - Log 7 variables. + TRACE_8(MESSAGE,A,B,C,D,E,F,G,H) - Log 8 variables. + TRACE_9(MESSAGE,A,B,C,D,E,F,G,H,I) - Log 9 variables. + +Parameters: + MESSAGE - Message to add to the trace [String] + A..H - Variable names to log values of [Any] + +Example: + (begin example) + TRACE_3("After takeoff",_vehicle player,getPos (_vehicle player), getPosASL (_vehicle player)); + (end) + +Author: + Spooner +------------------------------------------- */ +#define PFORMAT_1(MESSAGE,A) \ + format ['%1: A=%2', MESSAGE, RETNIL(A)] + +#define PFORMAT_2(MESSAGE,A,B) \ + format ['%1: A=%2, B=%3', MESSAGE, RETNIL(A), RETNIL(B)] + +#define PFORMAT_3(MESSAGE,A,B,C) \ + format ['%1: A=%2, B=%3, C=%4', MESSAGE, RETNIL(A), RETNIL(B), RETNIL(C)] + +#define PFORMAT_4(MESSAGE,A,B,C,D) \ + format ['%1: A=%2, B=%3, C=%4, D=%5', MESSAGE, RETNIL(A), RETNIL(B), RETNIL(C), RETNIL(D)] + +#define PFORMAT_5(MESSAGE,A,B,C,D,E) \ + format ['%1: A=%2, B=%3, C=%4, D=%5, E=%6', MESSAGE, RETNIL(A), RETNIL(B), RETNIL(C), RETNIL(D), RETNIL(E)] + +#define PFORMAT_6(MESSAGE,A,B,C,D,E,F) \ + format ['%1: A=%2, B=%3, C=%4, D=%5, E=%6, F=%7', MESSAGE, RETNIL(A), RETNIL(B), RETNIL(C), RETNIL(D), RETNIL(E), RETNIL(F)] + +#define PFORMAT_7(MESSAGE,A,B,C,D,E,F,G) \ + format ['%1: A=%2, B=%3, C=%4, D=%5, E=%6, F=%7, G=%8', MESSAGE, RETNIL(A), RETNIL(B), RETNIL(C), RETNIL(D), RETNIL(E), RETNIL(F), RETNIL(G)] + +#define PFORMAT_8(MESSAGE,A,B,C,D,E,F,G,H) \ + format ['%1: A=%2, B=%3, C=%4, D=%5, E=%6, F=%7, G=%8, H=%9', MESSAGE, RETNIL(A), RETNIL(B), RETNIL(C), RETNIL(D), RETNIL(E), RETNIL(F), RETNIL(G), RETNIL(H)] + +#define PFORMAT_9(MESSAGE,A,B,C,D,E,F,G,H,I) \ + format ['%1: A=%2, B=%3, C=%4, D=%5, E=%6, F=%7, G=%8, H=%9, I=%10', MESSAGE, RETNIL(A), RETNIL(B), RETNIL(C), RETNIL(D), RETNIL(E), RETNIL(F), RETNIL(G), RETNIL(H), RETNIL(I)] + + +#ifdef DEBUG_MODE_FULL +#define TRACE_1(MESSAGE,A) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_1(str diag_frameNo + ' ' + (MESSAGE),A)) +#define TRACE_2(MESSAGE,A,B) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_2(str diag_frameNo + ' ' + (MESSAGE),A,B)) +#define TRACE_3(MESSAGE,A,B,C) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_3(str diag_frameNo + ' ' + (MESSAGE),A,B,C)) +#define TRACE_4(MESSAGE,A,B,C,D) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_4(str diag_frameNo + ' ' + (MESSAGE),A,B,C,D)) +#define TRACE_5(MESSAGE,A,B,C,D,E) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_5(str diag_frameNo + ' ' + (MESSAGE),A,B,C,D,E)) +#define TRACE_6(MESSAGE,A,B,C,D,E,F) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_6(str diag_frameNo + ' ' + (MESSAGE),A,B,C,D,E,F)) +#define TRACE_7(MESSAGE,A,B,C,D,E,F,G) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_7(str diag_frameNo + ' ' + (MESSAGE),A,B,C,D,E,F,G)) +#define TRACE_8(MESSAGE,A,B,C,D,E,F,G,H) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_8(str diag_frameNo + ' ' + (MESSAGE),A,B,C,D,E,F,G,H)) +#define TRACE_9(MESSAGE,A,B,C,D,E,F,G,H,I) LOG_SYS_FILELINENUMBERS('TRACE',PFORMAT_9(str diag_frameNo + ' ' + (MESSAGE),A,B,C,D,E,F,G,H,I)) +#else +#define TRACE_1(MESSAGE,A) /* disabled */ +#define TRACE_2(MESSAGE,A,B) /* disabled */ +#define TRACE_3(MESSAGE,A,B,C) /* disabled */ +#define TRACE_4(MESSAGE,A,B,C,D) /* disabled */ +#define TRACE_5(MESSAGE,A,B,C,D,E) /* disabled */ +#define TRACE_6(MESSAGE,A,B,C,D,E,F) /* disabled */ +#define TRACE_7(MESSAGE,A,B,C,D,E,F,G) /* disabled */ +#define TRACE_8(MESSAGE,A,B,C,D,E,F,G,H) /* disabled */ +#define TRACE_9(MESSAGE,A,B,C,D,E,F,G,H,I) /* disabled */ +#endif + +/* ------------------------------------------- +Group: General +------------------------------------------- */ + +// ************************************* +// Internal Functions +#define DOUBLES(var1,var2) var1##_##var2 +#define TRIPLES(var1,var2,var3) var1##_##var2##_##var3 +#define QUOTE(var1) #var1 + +#ifdef MODULAR + #define COMPONENT_T DOUBLES(t,COMPONENT) + #define COMPONENT_M DOUBLES(m,COMPONENT) + #define COMPONENT_S DOUBLES(s,COMPONENT) + #define COMPONENT_C DOUBLES(c,COMPONENT) + #define COMPONENT_F COMPONENT_C +#else + #define COMPONENT_T COMPONENT + #define COMPONENT_M COMPONENT + #define COMPONENT_S COMPONENT + #define COMPONENT_F COMPONENT + #define COMPONENT_C COMPONENT +#endif + +/* ------------------------------------------- +Macro: INC() + +Description: + Increase a number by one. + +Parameters: + VAR - Variable to increment [Number] + +Example: + (begin example) + _counter = 0; + INC(_counter); + // _counter => 1 + (end) + +Author: + Spooner +------------------------------------------- */ +#define INC(var) var = (var) + 1 + +/* ------------------------------------------- +Macro: DEC() + +Description: + Decrease a number by one. + +Parameters: + VAR - Variable to decrement [Number] + +Example: + (begin example) + _counter = 99; + DEC(_counter); + // _counter => 98 + (end) + +Author: + Spooner +------------------------------------------- */ +#define DEC(var) var = (var) - 1 + +/* ------------------------------------------- +Macro: ADD() + +Description: + Add a value to a variable. Variable and value should be both Numbers or both Strings. + +Parameters: + VAR - Variable to add to [Number or String] + VALUE - Value to add [Number or String] + +Examples: + (begin example) + _counter = 2; + ADD(_counter,3); + // _counter => 5 + (end) + (begin example) + _str = "hello"; + ADD(_str," "); + ADD(_str,"Fred"); + // _str => "hello Fred" + (end) + +Author: + Sickboy +------------------------------------------- */ +#define ADD(var1,var2) var1 = (var1) + (var2) + +/* ------------------------------------------- +Macro: SUB() + +Description: + Subtract a value from a number variable. VAR and VALUE should both be Numbers. + +Parameters: + VAR - Variable to subtract from [Number] + VALUE - Value to subtract [Number] + +Examples: + (begin example) + _numChickens = 2; + SUB(_numChickens,3); + // _numChickens => -1 + (end) +------------------------------------------- */ +#define SUB(var1,var2) var1 = (var1) - (var2) + +/* ------------------------------------------- +Macro: REM() + +Description: + Remove an element from an array each time it occurs. + + This recreates the entire array, so use BIS_fnc_removeIndex if modification of the original array is required + or if only one of the elements that matches ELEMENT needs to be removed. + +Parameters: + ARRAY - Array to modify [Array] + ELEMENT - Element to remove [Any] + +Examples: + (begin example) + _array = [1, 2, 3, 4, 3, 8]; + REM(_array,3); + // _array = [1, 2, 4, 8]; + (end) + +Author: + Spooner +------------------------------------------- */ +#define REM(var1,var2) SUB(var1,[var2]) + +/* ------------------------------------------- +Macro: PUSH() + +Description: + Appends a single value onto the end of an ARRAY. Change is made to the ARRAY itself, not creating a new array. + +Parameters: + ARRAY - Array to push element onto [Array] + ELEMENT - Element to push [Any] + +Examples: + (begin example) + _fish = ["blue", "green", "smelly"]; + PUSH(_fish,"monkey-flavoured"); + // _fish => ["blue", "green", "smelly", "monkey-flavoured"] + (end) + +Author: + Spooner +------------------------------------------- */ +#define PUSH(var1,var2) (var1) pushBack (var2) + +/* ------------------------------------------- +Macro: MAP() +Description: + Applies given code to each element of the array, then assigns the + resulting array to the original +Parameters: + ARRAY - Array to be modified + CODE - Code that'll be applied to each element of the array. +Example: + (begin example) + _array = [1, 2, 3, 4, 3, 8]; + MAP(_array,_x + 1); + // _array is now [2, 3, 4, 5, 4, 9]; + (end) +Author: + 654wak654 +------------------------------------------- */ +#define MAP(ARR,CODE) ARR = ARR apply {CODE} + +/* ------------------------------------------- +Macro: FILTER() +Description: + Filters an array based on given code, then assigns the resulting array + to the original +Parameters: + ARRAY - Array to be filtered + CODE - Condition to pick elements +Example: + (begin example) + _array = [1, 2, 3, 4, 3, 8]; + FILTER(_array,_x % 2 == 0) + // _array is now [2, 4, 8]; + (end) +Author: + Commy2 +------------------------------------------- */ +#define FILTER(ARR,CODE) ARR = ARR select {CODE} + +/* ------------------------------------------- +Macro: UNIQUE() +Description: + Removes duplicate values in given array +Parameters: + ARRAY - The array to be modified +Example: + (begin example) + _someArray = [4, 4, 5, 5, 5, 2]; + UNIQUE(_someArray); + // _someArray is now [4, 5, 2] + (end) +Author: + Commy2 +------------------------------------------- */ +#define UNIQUE(ARR) ARR = ARR arrayIntersect ARR + +/* ------------------------------------------- +Macro: INTERSECTION() +Description: + Finds unique common elements between two arrays and assigns them + to the first array +Parameters: + ARRAY0 - The array to be modified + ARRAY1 - The array to find intersections with +Example: + (begin example) + _someArray = [1, 2, 3, 4, 5, 5]; + _anotherArray = [4, 5, 6, 7]; + INTERSECTION(_someArray,_anotherArray); + // _someArray is now [4, 5] + (end) +Author: + 654wak654 +------------------------------------------- */ +#define INTERSECTION(ARG0,ARG1) ARG0 = ARG0 arrayIntersect (ARG1) + +/* ------------------------------------------- +Macro: ISNILS() + +Description: + Sets a variable with a value, but only if it is undefined. + +Parameters: + VARIABLE - Variable to set [Any, not nil] + DEFAULT_VALUE - Value to set VARIABLE to if it is undefined [Any, not nil] + +Examples: + (begin example) + // _fish is undefined + ISNILS(_fish,0); + // _fish => 0 + (end) + (begin example) + _fish = 12; + // ...later... + ISNILS(_fish,0); + // _fish => 12 + (end) + +Author: + Sickboy +------------------------------------------- */ +#define ISNILS(VARIABLE,DEFAULT_VALUE) if (isNil #VARIABLE) then { ##VARIABLE = ##DEFAULT_VALUE } +#define ISNILS2(var1,var2,var3,var4) ISNILS(TRIPLES(var1,var2,var3),var4) +#define ISNILS3(var1,var2,var3) ISNILS(DOUBLES(var1,var2),var3) +#define ISNIL(var1,var2) ISNILS2(PREFIX,COMPONENT,var1,var2) +#define ISNILMAIN(var1,var2) ISNILS3(PREFIX,var1,var2) + +#define CREATELOGICS(var1,var2) ##var1##_##var2## = ([sideLogic] call CBA_fnc_getSharedGroup) createUnit ["LOGIC", [0, 0, 0], [], 0, "NONE"] +#define CREATELOGICLOCALS(var1,var2) ##var1##_##var2## = "LOGIC" createVehicleLocal [0, 0, 0] +#define CREATELOGICGLOBALS(var1,var2) ##var1##_##var2## = ([sideLogic] call CBA_fnc_getSharedGroup) createUnit ["LOGIC", [0, 0, 0], [], 0, "NONE"]; publicVariable QUOTE(DOUBLES(var1,var2)) +#define CREATELOGICGLOBALTESTS(var1,var2) ##var1##_##var2## = ([sideLogic] call CBA_fnc_getSharedGroup) createUnit [QUOTE(DOUBLES(ADDON,logic)), [0, 0, 0], [], 0, "NONE"] + +#define GETVARS(var1,var2,var3) (##var1##_##var2 getVariable #var3) +#define GETVARMAINS(var1,var2) GETVARS(var1,MAINLOGIC,var2) + +#ifndef PATHTO_SYS + #define PATHTO_SYS(var1,var2,var3) \MAINPREFIX\##var1\SUBPREFIX\##var2\##var3.sqf +#endif +#ifndef PATHTOF_SYS + #define PATHTOF_SYS(var1,var2,var3) \MAINPREFIX\##var1\SUBPREFIX\##var2\##var3 +#endif + +#ifndef PATHTOF2_SYS + #define PATHTOF2_SYS(var1,var2,var3) MAINPREFIX\##var1\SUBPREFIX\##var2\##var3 +#endif + +#define PATHTO_R(var1) PATHTOF2_SYS(PREFIX,COMPONENT_C,var1) +#define PATHTO_T(var1) PATHTOF_SYS(PREFIX,COMPONENT_T,var1) +#define PATHTO_M(var1) PATHTOF_SYS(PREFIX,COMPONENT_M,var1) +#define PATHTO_S(var1) PATHTOF_SYS(PREFIX,COMPONENT_S,var1) +#define PATHTO_C(var1) PATHTOF_SYS(PREFIX,COMPONENT_C,var1) +#define PATHTO_F(var1) PATHTO_SYS(PREFIX,COMPONENT_F,var1) + +// Already quoted "" +#define QPATHTO_R(var1) QUOTE(PATHTO_R(var1)) +#define QPATHTO_T(var1) QUOTE(PATHTO_T(var1)) +#define QPATHTO_M(var1) QUOTE(PATHTO_M(var1)) +#define QPATHTO_S(var1) QUOTE(PATHTO_S(var1)) +#define QPATHTO_C(var1) QUOTE(PATHTO_C(var1)) +#define QPATHTO_F(var1) QUOTE(PATHTO_F(var1)) + +// This only works for binarized configs after recompiling the pbos +// TODO: Reduce amount of calls / code.. +#define COMPILE_FILE2_CFG_SYS(var1) compile preProcessFileLineNumbers var1 +#define COMPILE_FILE2_SYS(var1) COMPILE_FILE2_CFG_SYS(var1) + +#define COMPILE_FILE_SYS(var1,var2,var3) COMPILE_FILE2_SYS('PATHTO_SYS(var1,var2,var3)') +#define COMPILE_FILE_CFG_SYS(var1,var2,var3) COMPILE_FILE2_CFG_SYS('PATHTO_SYS(var1,var2,var3)') + +#define SETVARS(var1,var2) ##var1##_##var2 setVariable +#define SETVARMAINS(var1) SETVARS(var1,MAINLOGIC) +#define GVARMAINS(var1,var2) ##var1##_##var2## +#define CFGSETTINGSS(var1,var2) configFile >> "CfgSettings" >> #var1 >> #var2 +//#define SETGVARS(var1,var2,var3) ##var1##_##var2##_##var3 = +//#define SETGVARMAINS(var1,var2) ##var1##_##var2 = + +// Compile-Once, JIT: On first use. +// #define PREPMAIN_SYS(var1,var2,var3) ##var1##_fnc_##var3 = { ##var1##_fnc_##var3 = COMPILE_FILE_SYS(var1,var2,DOUBLES(fnc,var3)); if (isNil "_this") then { call ##var1##_fnc_##var3 } else { _this call ##var1##_fnc_##var3 } } +// #define PREP_SYS(var1,var2,var3) ##var1##_##var2##_fnc_##var3 = { ##var1##_##var2##_fnc_##var3 = COMPILE_FILE_SYS(var1,var2,DOUBLES(fnc,var3)); if (isNil "_this") then { call ##var1##_##var2##_fnc_##var3 } else { _this call ##var1##_##var2##_fnc_##var3 } } +// #define PREP_SYS2(var1,var2,var3,var4) ##var1##_##var2##_fnc_##var4 = { ##var1##_##var2##_fnc_##var4 = COMPILE_FILE_SYS(var1,var3,DOUBLES(fnc,var4)); if (isNil "_this") then { call ##var1##_##var2##_fnc_##var4 } else { _this call ##var1##_##var2##_fnc_##var4 } } + +// Compile-Once, at Macro. As opposed to Compile-Once, on first use. +#define PREPMAIN_SYS(var1,var2,var3) ##var1##_fnc_##var3 = COMPILE_FILE_SYS(var1,var2,DOUBLES(fnc,var3)) +#define PREP_SYS(var1,var2,var3) ##var1##_##var2##_fnc_##var3 = COMPILE_FILE_SYS(var1,var2,DOUBLES(fnc,var3)) +#define PREP_SYS2(var1,var2,var3,var4) ##var1##_##var2##_fnc_##var4 = COMPILE_FILE_SYS(var1,var3,DOUBLES(fnc,var4)) + +#define LSTR(var1) TRIPLES(ADDON,STR,var1) + +#ifndef DEBUG_SETTINGS + #define DEBUG_SETTINGS [false, true, false] +#endif + +#define MSG_INIT QUOTE(Initializing: ADDON version: VERSION) + +// ************************************* +// User Functions +#define CFGSETTINGS CFGSETTINGSS(PREFIX,COMPONENT) +#define PATHTO(var1) PATHTO_SYS(PREFIX,COMPONENT_F,var1) +#define PATHTOF(var1) PATHTOF_SYS(PREFIX,COMPONENT,var1) +#define PATHTOEF(var1,var2) PATHTOF_SYS(PREFIX,var1,var2) +#define QPATHTOF(var1) QUOTE(PATHTOF(var1)) +#define QPATHTOEF(var1,var2) QUOTE(PATHTOEF(var1,var2)) + +#define COMPILE_FILE(var1) COMPILE_FILE_SYS(PREFIX,COMPONENT_F,var1) +#define COMPILE_FILE_CFG(var1) COMPILE_FILE_CFG_SYS(PREFIX,COMPONENT_F,var1) +#define COMPILE_FILE2(var1) COMPILE_FILE2_SYS('var1') +#define COMPILE_FILE2_CFG(var1) COMPILE_FILE2_CFG_SYS('var1') + + +#define VERSIONING_SYS(var1) class CfgSettings \ +{ \ + class CBA \ + { \ + class Versioning \ + { \ + class var1 \ + { \ + }; \ + }; \ + }; \ +}; + +#define VERSIONING VERSIONING_SYS(PREFIX) + +/* ------------------------------------------- +Macro: GVAR() + Get full variable identifier for a global variable owned by this component. + +Parameters: + VARIABLE - Partial name of global variable owned by this component [Any]. + +Example: + (begin example) + GVAR(frog) = 12; + // In SPON_FrogDancing component, equivalent to SPON_FrogDancing_frog = 12 + (end) + +Author: + Sickboy +------------------------------------------- */ +#define GVAR(var1) DOUBLES(ADDON,var1) +#define EGVAR(var1,var2) TRIPLES(PREFIX,var1,var2) +#define QGVAR(var1) QUOTE(GVAR(var1)) +#define QEGVAR(var1,var2) QUOTE(EGVAR(var1,var2)) +#define QQGVAR(var1) QUOTE(QGVAR(var1)) +#define QQEGVAR(var1,var2) QUOTE(QEGVAR(var1,var2)) + +/* ------------------------------------------- +Macro: GVARMAIN() + Get full variable identifier for a global variable owned by this addon. + +Parameters: + VARIABLE - Partial name of global variable owned by this addon [Any]. + +Example: + (begin example) + GVARMAIN(frog) = 12; + // In SPON_FrogDancing component, equivalent to SPON_frog = 12 + (end) + +Author: + Sickboy +------------------------------------------- */ +#define GVARMAIN(var1) GVARMAINS(PREFIX,var1) +#define QGVARMAIN(var1) QUOTE(GVARMAIN(var1)) +#define QQGVARMAIN(var1) QUOTE(QGVARMAIN(var1)) +// TODO: What's this? +#define SETTINGS DOUBLES(PREFIX,settings) +#define CREATELOGIC CREATELOGICS(PREFIX,COMPONENT) +#define CREATELOGICGLOBAL CREATELOGICGLOBALS(PREFIX,COMPONENT) +#define CREATELOGICGLOBALTEST CREATELOGICGLOBALTESTS(PREFIX,COMPONENT) +#define CREATELOGICLOCAL CREATELOGICLOCALS(PREFIX,COMPONENT) +#define CREATELOGICMAIN CREATELOGICS(PREFIX,MAINLOGIC) +#define GETVAR(var1) GETVARS(PREFIX,COMPONENT,var1) +#define SETVAR SETVARS(PREFIX,COMPONENT) +#define SETVARMAIN SETVARMAINS(PREFIX) +#define IFCOUNT(var1,var2,var3) if (count ##var1 > ##var2) then { ##var3 = ##var1 select ##var2 }; + +//#define PREP(var1) PREP_SYS(PREFIX,COMPONENT_F,var1) + +#ifdef DISABLE_COMPILE_CACHE + #define PREP(var1) TRIPLES(ADDON,fnc,var1) = compile preProcessFileLineNumbers 'PATHTO_SYS(PREFIX,COMPONENT_F,DOUBLES(fnc,var1))' + #define PREPMAIN(var1) TRIPLES(PREFIX,fnc,var1) = compile preProcessFileLineNumbers 'PATHTO_SYS(PREFIX,COMPONENT_F,DOUBLES(fnc,var1))' +#else + #define PREP(var1) ['PATHTO_SYS(PREFIX,COMPONENT_F,DOUBLES(fnc,var1))', 'TRIPLES(ADDON,fnc,var1)'] call SLX_XEH_COMPILE_NEW + #define PREPMAIN(var1) ['PATHTO_SYS(PREFIX,COMPONENT_F,DOUBLES(fnc,var1))', 'TRIPLES(PREFIX,fnc,var1)'] call SLX_XEH_COMPILE_NEW +#endif + +#ifdef RECOMPILE + #undef RECOMPILE + #define RECOMPILE recompile = 1 +#else + #define RECOMPILE recompile = 0 +#endif + +/* ------------------------------------------- +Macro: PATHTO_FNC() + +Description: + Defines a function inside CfgFunctions. + + Full file path in addons: + '\MAINPREFIX\PREFIX\SUBPREFIX\COMPONENT\fnc_.sqf' + Define 'RECOMPILE' to enable recompiling. + +Parameters: + FUNCTION NAME - Name of the function, unquoted + +Examples: + (begin example) + // file name: fnc_addPerFrameHandler.sqf + class CfgFunctions { + class CBA { + class Misc { + PATHTO_FNC(addPerFrameHandler); + }; + }; + }; + // -> CBA_fnc_addPerFrameHandler + (end) + +Author: + dixon13, commy2 + ------------------------------------------- */ +#define PATHTO_FNC(func) class func {\ + file = QPATHTOF(DOUBLES(fnc,func).sqf);\ + RECOMPILE;\ +} + +#define FUNC(var1) TRIPLES(ADDON,fnc,var1) +#define FUNCMAIN(var1) TRIPLES(PREFIX,fnc,var1) +#define FUNC_INNER(var1,var2) TRIPLES(DOUBLES(PREFIX,var1),fnc,var2) +#define EFUNC(var1,var2) FUNC_INNER(var1,var2) +#define QFUNC(var1) QUOTE(FUNC(var1)) +#define QFUNCMAIN(var1) QUOTE(FUNCMAIN(var1)) +#define QFUNC_INNER(var1,var2) QUOTE(FUNC_INNER(var1,var2)) +#define QEFUNC(var1,var2) QUOTE(EFUNC(var1,var2)) +#define QQFUNC(var1) QUOTE(QFUNC(var1)) +#define QQFUNCMAIN(var1) QUOTE(QFUNCMAIN(var1)) +#define QQFUNC_INNER(var1,var2) QUOTE(QFUNC_INNER(var1,var2)) +#define QQEFUNC(var1,var2) QUOTE(QEFUNC(var1,var2)) + +#ifndef PRELOAD_ADDONS + #define PRELOAD_ADDONS class CfgAddons \ +{ \ + class PreloadAddons \ + { \ + class ADDON \ + { \ + list[]={ QUOTE(ADDON) }; \ + }; \ + }; \ +} +#endif + +/* ------------------------------------------- +Macros: ARG_#() + Select from list of array arguments + +Parameters: + VARIABLE(1-8) - elements for the list + +Author: + Rommel +------------------------------------------- */ +#define ARG_1(A,B) ((A) select (B)) +#define ARG_2(A,B,C) (ARG_1(ARG_1(A,B),C)) +#define ARG_3(A,B,C,D) (ARG_1(ARG_2(A,B,C),D)) +#define ARG_4(A,B,C,D,E) (ARG_1(ARG_3(A,B,C,D),E)) +#define ARG_5(A,B,C,D,E,F) (ARG_1(ARG_4(A,B,C,D,E),F)) +#define ARG_6(A,B,C,D,E,F,G) (ARG_1(ARG_5(A,B,C,D,E,F),G)) +#define ARG_7(A,B,C,D,E,F,G,H) (ARG_1(ARG_6(A,B,C,D,E,E,F,G),H)) +#define ARG_8(A,B,C,D,E,F,G,H,I) (ARG_1(ARG_7(A,B,C,D,E,E,F,G,H),I)) + +/* ------------------------------------------- +Macros: ARR_#() + Create list from arguments. Useful for working around , in macro parameters. + 1-8 arguments possible. + +Parameters: + VARIABLE(1-8) - elements for the list + +Author: + Nou +------------------------------------------- */ +#define ARR_1(ARG1) ARG1 +#define ARR_2(ARG1,ARG2) ARG1, ARG2 +#define ARR_3(ARG1,ARG2,ARG3) ARG1, ARG2, ARG3 +#define ARR_4(ARG1,ARG2,ARG3,ARG4) ARG1, ARG2, ARG3, ARG4 +#define ARR_5(ARG1,ARG2,ARG3,ARG4,ARG5) ARG1, ARG2, ARG3, ARG4, ARG5 +#define ARR_6(ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) ARG1, ARG2, ARG3, ARG4, ARG5, ARG6 +#define ARR_7(ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7 +#define ARR_8(ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8 + +/* ------------------------------------------- +Macros: FORMAT_#(STR, ARG1) + Format - Useful for working around , in macro parameters. + 1-8 arguments possible. + +Parameters: + STRING - string used by format + VARIABLE(1-8) - elements for usage in format + +Author: + Nou & Sickboy +------------------------------------------- */ +#define FORMAT_1(STR,ARG1) format[STR, ARG1] +#define FORMAT_2(STR,ARG1,ARG2) format[STR, ARG1, ARG2] +#define FORMAT_3(STR,ARG1,ARG2,ARG3) format[STR, ARG1, ARG2, ARG3] +#define FORMAT_4(STR,ARG1,ARG2,ARG3,ARG4) format[STR, ARG1, ARG2, ARG3, ARG4] +#define FORMAT_5(STR,ARG1,ARG2,ARG3,ARG4,ARG5) format[STR, ARG1, ARG2, ARG3, ARG4, ARG5] +#define FORMAT_6(STR,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6) format[STR, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6] +#define FORMAT_7(STR,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7) format[STR, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7] +#define FORMAT_8(STR,ARG1,ARG2,ARG3,ARG4,ARG5,ARG6,ARG7,ARG8) format[STR, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8] + +// CONTROL(46) 12 +#define DISPLAY(A) (findDisplay A) +#define CONTROL(A) DISPLAY(A) displayCtrl + +/* ------------------------------------------- +Macros: IS_x() + Checking the data types of variables. + + IS_ARRAY() - Array + IS_BOOL() - Boolean + IS_BOOLEAN() - UI display handle(synonym for ) + IS_CODE() - Code block (i.e a compiled function) + IS_CONFIG() - Configuration + IS_CONTROL() - UI control handle. + IS_DISPLAY() - UI display handle. + IS_FUNCTION() - A compiled function (synonym for ) + IS_GROUP() - Group. + IS_INTEGER() - Is a number a whole number? + IS_LOCATION() - World location. + IS_NUMBER() - A floating point number (synonym for ) + IS_OBJECT() - World object. + IS_SCALAR() - Floating point number. + IS_SCRIPT() - A script handle (as returned by execVM and spawn commands). + IS_SIDE() - Game side. + IS_STRING() - World object. + IS_TEXT() - Structured text. + +Parameters: + VARIABLE - Variable to check if it is of a particular type [Any, not nil] + +Author: + Spooner +------------------------------------------- */ +#define IS_META_SYS(VAR,TYPE) (if (isNil {VAR}) then {false} else {(VAR) isEqualType TYPE}) +#define IS_ARRAY(VAR) IS_META_SYS(VAR,[]) +#define IS_BOOL(VAR) IS_META_SYS(VAR,false) +#define IS_CODE(VAR) IS_META_SYS(VAR,{}) +#define IS_CONFIG(VAR) IS_META_SYS(VAR,configNull) +#define IS_CONTROL(VAR) IS_META_SYS(VAR,controlNull) +#define IS_DISPLAY(VAR) IS_META_SYS(VAR,displayNull) +#define IS_GROUP(VAR) IS_META_SYS(VAR,grpNull) +#define IS_OBJECT(VAR) IS_META_SYS(VAR,objNull) +#define IS_SCALAR(VAR) IS_META_SYS(VAR,0) +#define IS_SCRIPT(VAR) IS_META_SYS(VAR,scriptNull) +#define IS_SIDE(VAR) IS_META_SYS(VAR,west) +#define IS_STRING(VAR) IS_META_SYS(VAR,"STRING") +#define IS_TEXT(VAR) IS_META_SYS(VAR,text "") +#define IS_LOCATION(VAR) IS_META_SYS(VAR,locationNull) + +#define IS_BOOLEAN(VAR) IS_BOOL(VAR) +#define IS_FUNCTION(VAR) IS_CODE(VAR) +#define IS_INTEGER(VAR) (if (IS_SCALAR(VAR)) then {floor (VAR) == (VAR)} else {false}) +#define IS_NUMBER(VAR) IS_SCALAR(VAR) + +#define FLOAT_TO_STRING(num) (if (_this == 0) then {"0"} else {str parseNumber (str (_this % _this) + str floor abs _this) + "." + (str (abs _this - floor abs _this) select [2]) + "0"}) + +/* ------------------------------------------- +Macro: SCRIPT() + Sets name of script (relies on PREFIX and COMPONENT values being #defined). + +Parameters: + NAME - Name of script [Indentifier] + +Example: + (begin example) + SCRIPT(eradicateMuppets); + (end) + +Author: + Spooner +------------------------------------------- */ +#define SCRIPT(NAME) \ + scriptName 'PREFIX\COMPONENT\NAME' + +/* ------------------------------------------- +Macros: EXPLODE_n() + DEPRECATED - Use param/params commands added in Arma 3 1.48 + + Splitting an ARRAY into a number of variables (A, B, C, etc). + + Note that this NOT does make the created variables private. + _PVT variants do. + + EXPLODE_1(ARRAY,A,B) - Split a 1-element array into separate variable. + EXPLODE_2(ARRAY,A,B) - Split a 2-element array into separate variables. + EXPLODE_3(ARRAY,A,B,C) - Split a 3-element array into separate variables. + EXPLODE_4(ARRAY,A,B,C,D) - Split a 4-element array into separate variables. + EXPLODE_5(ARRAY,A,B,C,D,E) - Split a 5-element array into separate variables. + EXPLODE_6(ARRAY,A,B,C,D,E,F) - Split a 6-element array into separate variables. + EXPLODE_7(ARRAY,A,B,C,D,E,F,G) - Split a 7-element array into separate variables. + EXPLODE_8(ARRAY,A,B,C,D,E,F,G,H) - Split a 8-element array into separate variables. + EXPLODE_9(ARRAY,A,B,C,D,E,F,G,H,I) - Split a 9-element array into separate variables. + +Parameters: + ARRAY - Array to read from [Array] + A..H - Names of variables to set from array [Identifier] + +Example: + (begin example) + _array = ["fred", 156.8, 120.9]; + EXPLODE_3(_array,_name,_height,_weight); + (end) + +Author: + Spooner +------------------------------------------- */ +#define EXPLODE_1_SYS(ARRAY,A) A = ARRAY param [0] +#define EXPLODE_1(ARRAY,A) EXPLODE_1_SYS(ARRAY,A); TRACE_1("EXPLODE_1, " + QUOTE(ARRAY),A) +#define EXPLODE_1_PVT(ARRAY,A) ARRAY params [#A]; TRACE_1("EXPLODE_1, " + QUOTE(ARRAY),A) + +#define EXPLODE_2_SYS(ARRAY,A,B) EXPLODE_1_SYS(ARRAY,A); B = ARRAY param [1] +#define EXPLODE_2(ARRAY,A,B) EXPLODE_2_SYS(ARRAY,A,B); TRACE_2("EXPLODE_2, " + QUOTE(ARRAY),A,B) +#define EXPLODE_2_PVT(ARRAY,A,B) ARRAY params [#A,#B]; TRACE_2("EXPLODE_2, " + QUOTE(ARRAY),A,B) + +#define EXPLODE_3_SYS(ARRAY,A,B,C) EXPLODE_2_SYS(ARRAY,A,B); C = ARRAY param [2] +#define EXPLODE_3(ARRAY,A,B,C) EXPLODE_3_SYS(ARRAY,A,B,C); TRACE_3("EXPLODE_3, " + QUOTE(ARRAY),A,B,C) +#define EXPLODE_3_PVT(ARRAY,A,B,C) ARRAY params [#A,#B,#C]; TRACE_3("EXPLODE_3, " + QUOTE(ARRAY),A,B,C) + +#define EXPLODE_4_SYS(ARRAY,A,B,C,D) EXPLODE_3_SYS(ARRAY,A,B,C); D = ARRAY param [3] +#define EXPLODE_4(ARRAY,A,B,C,D) EXPLODE_4_SYS(ARRAY,A,B,C,D); TRACE_4("EXPLODE_4, " + QUOTE(ARRAY),A,B,C,D) +#define EXPLODE_4_PVT(ARRAY,A,B,C,D) ARRAY params [#A,#B,#C,#D]; TRACE_4("EXPLODE_4, " + QUOTE(ARRAY),A,B,C,D) + +#define EXPLODE_5_SYS(ARRAY,A,B,C,D,E) EXPLODE_4_SYS(ARRAY,A,B,C,D); E = ARRAY param [4] +#define EXPLODE_5(ARRAY,A,B,C,D,E) EXPLODE_5_SYS(ARRAY,A,B,C,D,E); TRACE_5("EXPLODE_5, " + QUOTE(ARRAY),A,B,C,D,E) +#define EXPLODE_5_PVT(ARRAY,A,B,C,D,E) ARRAY params [#A,#B,#C,#D,#E]; TRACE_5("EXPLODE_5, " + QUOTE(ARRAY),A,B,C,D,E) + +#define EXPLODE_6_SYS(ARRAY,A,B,C,D,E,F) EXPLODE_5_SYS(ARRAY,A,B,C,D,E); F = ARRAY param [5] +#define EXPLODE_6(ARRAY,A,B,C,D,E,F) EXPLODE_6_SYS(ARRAY,A,B,C,D,E,F); TRACE_6("EXPLODE_6, " + QUOTE(ARRAY),A,B,C,D,E,F) +#define EXPLODE_6_PVT(ARRAY,A,B,C,D,E,F) ARRAY params [#A,#B,#C,#D,#E,#F]; TRACE_6("EXPLODE_6, " + QUOTE(ARRAY),A,B,C,D,E,F) + +#define EXPLODE_7_SYS(ARRAY,A,B,C,D,E,F,G) EXPLODE_6_SYS(ARRAY,A,B,C,D,E,F); G = ARRAY param [6] +#define EXPLODE_7(ARRAY,A,B,C,D,E,F,G) EXPLODE_7_SYS(ARRAY,A,B,C,D,E,F,G); TRACE_7("EXPLODE_7, " + QUOTE(ARRAY),A,B,C,D,E,F,G) +#define EXPLODE_7_PVT(ARRAY,A,B,C,D,E,F,G) ARRAY params [#A,#B,#C,#D,#E,#F,#G]; TRACE_7("EXPLODE_7, " + QUOTE(ARRAY),A,B,C,D,E,F,G) + +#define EXPLODE_8_SYS(ARRAY,A,B,C,D,E,F,G,H) EXPLODE_7_SYS(ARRAY,A,B,C,D,E,F,G); H = ARRAY param [7] +#define EXPLODE_8(ARRAY,A,B,C,D,E,F,G,H) EXPLODE_8_SYS(ARRAY,A,B,C,D,E,F,G,H); TRACE_8("EXPLODE_8, " + QUOTE(ARRAY),A,B,C,D,E,F,G,H) +#define EXPLODE_8_PVT(ARRAY,A,B,C,D,E,F,G,H) ARRAY params [#A,#B,#C,#D,#E,#F,#G,#H]; TRACE_8("EXPLODE_8, " + QUOTE(ARRAY),A,B,C,D,E,F,G,H) + +#define EXPLODE_9_SYS(ARRAY,A,B,C,D,E,F,G,H,I) EXPLODE_8_SYS(ARRAY,A,B,C,D,E,F,G,H); I = ARRAY param [8] +#define EXPLODE_9(ARRAY,A,B,C,D,E,F,G,H,I) EXPLODE_9_SYS(ARRAY,A,B,C,D,E,F,G,H,I); TRACE_9("EXPLODE_9, " + QUOTE(ARRAY),A,B,C,D,E,F,G,H,I) +#define EXPLODE_9_PVT(ARRAY,A,B,C,D,E,F,G,H,I) ARRAY params [#A,#B,#C,#D,#E,#F,#G,#H,#I]; TRACE_9("EXPLODE_9, " + QUOTE(ARRAY),A,B,C,D,E,F,G,H,I) + +/* ------------------------------------------- +Macro: xSTRING() + Get full string identifier from a stringtable owned by this component. + +Parameters: + VARIABLE - Partial name of global variable owned by this component [Any]. + +Example: + ADDON is CBA_Balls. + (begin example) + // Localized String (localize command must still be used with it) + LSTRING(Example); // STR_CBA_Balls_Example; + // Config String (note the $) + CSTRING(Example); // $STR_CBA_Balls_Example; + (end) + +Author: + Jonpas +------------------------------------------- */ +#ifndef STRING_MACROS_GUARD +#define STRING_MACROS_GUARD + #define LSTRING(var1) QUOTE(TRIPLES(STR,ADDON,var1)) + #define ELSTRING(var1,var2) QUOTE(TRIPLES(STR,DOUBLES(PREFIX,var1),var2)) + #define CSTRING(var1) QUOTE(TRIPLES($STR,ADDON,var1)) + #define ECSTRING(var1,var2) QUOTE(TRIPLES($STR,DOUBLES(PREFIX,var1),var2)) + + #define LLSTRING(var1) localize QUOTE(TRIPLES(STR,ADDON,var1)) + #define LELSTRING(var1,var2) localize QUOTE(TRIPLES(STR,DOUBLES(PREFIX,var1),var2)) +#endif + + +/* ------------------------------------------- +Group: Managing Function Parameters +------------------------------------------- */ + +/* ------------------------------------------- +Macros: PARAMS_n() + DEPRECATED - Use param/params commands added in Arma 3 1.48 + + Setting variables based on parameters passed to a function. + + Each parameter is defines as private and set to the appropriate value from _this. + + PARAMS_1(A) - Get 1 parameter from the _this array (or _this if it's not an array). + PARAMS_2(A,B) - Get 2 parameters from the _this array. + PARAMS_3(A,B,C) - Get 3 parameters from the _this array. + PARAMS_4(A,B,C,D) - Get 4 parameters from the _this array. + PARAMS_5(A,B,C,D,E) - Get 5 parameters from the _this array. + PARAMS_6(A,B,C,D,E,F) - Get 6 parameters from the _this array. + PARAMS_7(A,B,C,D,E,F,G) - Get 7 parameters from the _this array. + PARAMS_8(A,B,C,D,E,F,G,H) - Get 8 parameters from the _this array. + +Parameters: + A..H - Name of variable to read from _this [Identifier] + +Example: + A function called like this: + (begin example) + [_name,_address,_telephone] call recordPersonalDetails; + (end) + expects 3 parameters and those variables could be initialised at the start of the function definition with: + (begin example) + recordPersonalDetails = { + PARAMS_3(_name,_address,_telephone); + // Rest of function follows... + }; + (end) + +Author: + Spooner +------------------------------------------- */ +#define PARAMS_1(A) EXPLODE_1_PVT(_this,A) +#define PARAMS_2(A,B) EXPLODE_2_PVT(_this,A,B) +#define PARAMS_3(A,B,C) EXPLODE_3_PVT(_this,A,B,C) +#define PARAMS_4(A,B,C,D) EXPLODE_4_PVT(_this,A,B,C,D) +#define PARAMS_5(A,B,C,D,E) EXPLODE_5_PVT(_this,A,B,C,D,E) +#define PARAMS_6(A,B,C,D,E,F) EXPLODE_6_PVT(_this,A,B,C,D,E,F) +#define PARAMS_7(A,B,C,D,E,F,G) EXPLODE_7_PVT(_this,A,B,C,D,E,F,G) +#define PARAMS_8(A,B,C,D,E,F,G,H) EXPLODE_8_PVT(_this,A,B,C,D,E,F,G,H) +#define PARAMS_9(A,B,C,D,E,F,G,H,I) EXPLODE_9_PVT(_this,A,B,C,D,E,F,G,H,I) + +/* ------------------------------------------- +Macro: DEFAULT_PARAM() + DEPRECATED - Use param/params commands added in Arma 3 1.48 + + Getting a default function parameter. This may be used together with to have a mix of required and + optional parameters. + +Parameters: + INDEX - Index of parameter in _this [Integer, 0+] + NAME - Name of the variable to set [Identifier] + DEF_VALUE - Default value to use in case the array is too short or the value at INDEX is nil [Any] + +Example: + A function called with optional parameters: + (begin example) + [_name] call myFunction; + [_name, _numberOfLegs] call myFunction; + [_name, _numberOfLegs, _hasAHead] call myFunction; + (end) + 1 required parameter and 2 optional parameters. Those variables could be initialised at the start of the function + definition with: + (begin example) + myFunction = { + PARAMS_1(_name); + DEFAULT_PARAM(1,_numberOfLegs,2); + DEFAULT_PARAM(2,_hasAHead,true); + // Rest of function follows... + }; + (end) + +Author: + Spooner +------------------------------------------- */ +#define DEFAULT_PARAM(INDEX,NAME,DEF_VALUE) \ + private [#NAME,"_this"]; \ + ISNILS(_this,[]); \ + NAME = _this param [INDEX, DEF_VALUE]; \ + TRACE_3("DEFAULT_PARAM",INDEX,NAME,DEF_VALUE) + +/* ------------------------------------------- +Macro: KEY_PARAM() + Get value from key in _this list, return default when key is not included in list. + +Parameters: + KEY - Key name [String] + NAME - Name of the variable to set [Identifier] + DEF_VALUE - Default value to use in case key not found [ANY] + +Example: + + +Author: + Muzzleflash +------------------------------------------- */ +#define KEY_PARAM(KEY,NAME,DEF_VALUE) \ + private #NAME; \ + NAME = [toLower KEY, toUpper KEY, DEF_VALUE, RETNIL(_this)] call CBA_fnc_getArg; \ + TRACE_3("KEY_PARAM",KEY,NAME,DEF_VALUE) + +/* ------------------------------------------- +Group: Assertions +------------------------------------------- */ + +#define ASSERTION_ERROR(MESSAGE) ERROR_WITH_TITLE("Assertion failed!",MESSAGE) + +/* ------------------------------------------- +Macro: ASSERT_TRUE() + Asserts that a CONDITION is true. When an assertion fails, an error is raised with the given MESSAGE. + +Parameters: + CONDITION - Condition to assert as true [Boolean] + MESSSAGE - Message to display if (A OPERATOR B) is false [String] + +Example: + (begin example) + ASSERT_TRUE(_frogIsDead,"The frog is alive"); + (end) + +Author: + Spooner +------------------------------------------- */ +#define ASSERT_TRUE(CONDITION,MESSAGE) \ + if (not (CONDITION)) then \ + { \ + ASSERTION_ERROR('Assertion (CONDITION) failed!\n\n' + (MESSAGE)); \ + } + +/* ------------------------------------------- +Macro: ASSERT_FALSE() + Asserts that a CONDITION is false. When an assertion fails, an error is raised with the given MESSAGE. + +Parameters: + CONDITION - Condition to assert as false [Boolean] + MESSSAGE - Message to display if (A OPERATOR B) is true [String] + +Example: + (begin example) + ASSERT_FALSE(_frogIsDead,"The frog died"); + (end) + +Author: + Spooner +------------------------------------------- */ +#define ASSERT_FALSE(CONDITION,MESSAGE) \ + if (CONDITION) then \ + { \ + ASSERTION_ERROR('Assertion (not (CONDITION)) failed!\n\n' + (MESSAGE)) \ + } + +/* ------------------------------------------- +Macro: ASSERT_OP() + Asserts that (A OPERATOR B) is true. When an assertion fails, an error is raised with the given MESSAGE. + +Parameters: + A - First value [Any] + OPERATOR - Binary operator to use [Operator] + B - Second value [Any] + MESSSAGE - Message to display if (A OPERATOR B) is false. [String] + +Example: + (begin example) + ASSERT_OP(_fish,>,5,"Too few fish!"); + (end) + +Author: + Spooner +------------------------------------------- */ +#define ASSERT_OP(A,OPERATOR,B,MESSAGE) \ + if (not ((A) OPERATOR (B))) then \ + { \ + ASSERTION_ERROR('Assertion (A OPERATOR B) failed!\n' + 'A: ' + (str (A)) + '\n' + 'B: ' + (str (B)) + "\n\n" + (MESSAGE)); \ + } + +/* ------------------------------------------- +Macro: ASSERT_DEFINED() + Asserts that a VARIABLE is defined. When an assertion fails, an error is raised with the given MESSAGE.. + +Parameters: + VARIABLE - Variable to test if defined [String or Function]. + MESSAGE - Message to display if variable is undefined [String]. + +Examples: + (begin example) + ASSERT_DEFINED("_anUndefinedVar","Too few fish!"); + ASSERT_DEFINED({ obj getVariable "anUndefinedVar" },"Too many fish!"); + (end) + +Author: + Spooner +------------------------------------------- */ +#define ASSERT_DEFINED(VARIABLE,MESSAGE) \ + if (isNil VARIABLE) then \ + { \ + ASSERTION_ERROR('Assertion (VARIABLE is defined) failed!\n\n' + (MESSAGE)); \ + } + +/* ------------------------------------------- +Group: Unit tests +------------------------------------------- */ +#define TEST_SUCCESS(MESSAGE) MESSAGE_WITH_TITLE("Test OK",MESSAGE) +#define TEST_FAIL(MESSAGE) ERROR_WITH_TITLE("Test FAIL",MESSAGE) + +/* ------------------------------------------- +Macro: TEST_TRUE() + Tests that a CONDITION is true. + If the condition is not true, an error is raised with the given MESSAGE. + +Parameters: + CONDITION - Condition to assert as true [Boolean] + MESSSAGE - Message to display if (A OPERATOR B) is false [String] + +Example: + (begin example) + TEST_TRUE(_frogIsDead,"The frog is alive"); + (end) + +Author: + Killswitch +------------------------------------------- */ +#define TEST_TRUE(CONDITION, MESSAGE) \ + if (CONDITION) then \ + { \ + TEST_SUCCESS('(CONDITION)'); \ + } \ + else \ + { \ + TEST_FAIL('(CONDITION) ' + (MESSAGE)); \ + } + +/* ------------------------------------------- +Macro: TEST_FALSE() + Tests that a CONDITION is false. + If the condition is not false, an error is raised with the given MESSAGE. + +Parameters: + CONDITION - Condition to test as false [Boolean] + MESSSAGE - Message to display if (A OPERATOR B) is true [String] + +Example: + (begin example) + TEST_FALSE(_frogIsDead,"The frog died"); + (end) + +Author: + Killswitch +------------------------------------------- */ +#define TEST_FALSE(CONDITION, MESSAGE) \ + if (not (CONDITION)) then \ + { \ + TEST_SUCCESS('(not (CONDITION))'); \ + } \ + else \ + { \ + TEST_FAIL('(not (CONDITION)) ' + (MESSAGE)); \ + } + +/* ------------------------------------------- +Macro: TEST_OP() + Tests that (A OPERATOR B) is true. + If the test fails, an error is raised with the given MESSAGE. + +Parameters: + A - First value [Any] + OPERATOR - Binary operator to use [Operator] + B - Second value [Any] + MESSSAGE - Message to display if (A OPERATOR B) is false. [String] + +Example: + (begin example) + TEST_OP(_fish,>,5,"Too few fish!"); + (end) + +Author: + Killswitch +------------------------------------------- */ +#define TEST_OP(A,OPERATOR,B,MESSAGE) \ + if ((A) OPERATOR (B)) then \ + { \ + TEST_SUCCESS('(A OPERATOR B)') \ + } \ + else \ + { \ + TEST_FAIL('(A OPERATOR B)') \ + }; + +/* ------------------------------------------- +Macro: TEST_DEFINED_AND_OP() + Tests that A and B are defined and (A OPERATOR B) is true. + If the test fails, an error is raised with the given MESSAGE. + +Parameters: + A - First value [Any] + OPERATOR - Binary operator to use [Operator] + B - Second value [Any] + MESSSAGE - Message to display [String] + +Example: + (begin example) + TEST_OP(_fish,>,5,"Too few fish!"); + (end) + +Author: + Killswitch, PabstMirror +------------------------------------------- */ +#define TEST_DEFINED_AND_OP(A,OPERATOR,B,MESSAGE) \ + if (isNil #A) then { \ + TEST_FAIL('(A is not defined) ' + (MESSAGE)); \ + } else { \ + if (isNil #B) then { \ + TEST_FAIL('(B is not defined) ' + (MESSAGE)); \ + } else { \ + if ((A) OPERATOR (B)) then { \ + TEST_SUCCESS('(A OPERATOR B) ' + (MESSAGE)) \ + } else { \ + TEST_FAIL('(A OPERATOR B) ' + (MESSAGE)) \ + }; }; }; + + +/* ------------------------------------------- +Macro: TEST_DEFINED() + Tests that a VARIABLE is defined. + +Parameters: + VARIABLE - Variable to test if defined [String or Function]. + MESSAGE - Message to display if variable is undefined [String]. + +Examples: + (begin example) + TEST_DEFINED("_anUndefinedVar","Too few fish!"); + TEST_DEFINED({ obj getVariable "anUndefinedVar" },"Too many fish!"); + (end) + +Author: + Killswitch +------------------------------------------- */ +#define TEST_DEFINED(VARIABLE,MESSAGE) \ + if (not isNil VARIABLE) then \ + { \ + TEST_SUCCESS('(' + VARIABLE + ' is defined)'); \ + } \ + else \ + { \ + TEST_FAIL('(' + VARIABLE + ' is not defined)' + (MESSAGE)); \ + } + +/* ------------------------------------------- +Group: Managing Deprecation +------------------------------------------- */ + +/* ------------------------------------------- +Macro: DEPRECATE_SYS() + Allow deprecation of a function that has been renamed. + + Replaces an old OLD_FUNCTION (which will have PREFIX_ prepended) with a NEW_FUNCTION + (PREFIX_ prepended) with the intention that the old function will be disabled in the future. + + Shows a warning in RPT each time the deprecated function is used, but runs the new function. + +Parameters: + OLD_FUNCTION - Full name of old function [Identifier for function that does not exist any more] + NEW_FUNCTION - Full name of new function [Function] + +Example: + (begin example) + // After renaming CBA_fnc_frog as CBA_fnc_fish + DEPRECATE_SYS(CBA_fnc_frog,CBA_fnc_fish); + (end) + +Author: + Sickboy +------------------------------------------- */ +#define DEPRECATE_SYS(OLD_FUNCTION,NEW_FUNCTION) \ + OLD_FUNCTION = { \ + WARNING('Deprecated function used: OLD_FUNCTION (new: NEW_FUNCTION) in ADDON'); \ + if (isNil "_this") then { call NEW_FUNCTION } else { _this call NEW_FUNCTION }; \ + } + +/* ------------------------------------------- +Macro: DEPRECATE() + Allow deprecation of a function, in the current component, that has been renamed. + + Replaces an OLD_FUNCTION (which will have PREFIX_ prepended) with a NEW_FUNCTION + (PREFIX_ prepended) with the intention that the old function will be disabled in the future. + + Shows a warning in RPT each time the deprecated function is used, but runs the new function. + +Parameters: + OLD_FUNCTION - Name of old function, assuming PREFIX [Identifier for function that does not exist any more] + NEW_FUNCTION - Name of new function, assuming PREFIX [Function] + +Example: + (begin example) + // After renaming CBA_fnc_frog as CBA_fnc_fish + DEPRECATE(fnc_frog,fnc_fish); + (end) + +Author: + Sickboy +------------------------------------------- */ +#define DEPRECATE(OLD_FUNCTION,NEW_FUNCTION) \ + DEPRECATE_SYS(DOUBLES(PREFIX,OLD_FUNCTION),DOUBLES(PREFIX,NEW_FUNCTION)) + +/* ------------------------------------------- +Macro: OBSOLETE_SYS() + Replace a function that has become obsolete. + + Replace an obsolete OLD_FUNCTION with a simple COMMAND_FUNCTION, with the intention that anyone + using the function should replace it with the simple command, since the function will be disabled in the future. + + Shows a warning in RPT each time the deprecated function is used, and runs the command function. + +Parameters: + OLD_FUNCTION - Full name of old function [Identifier for function that does not exist any more] + COMMAND_CODE - Code to replace the old function [Function] + +Example: + (begin example) + // In Arma2, currentWeapon command made the CBA_fMyWeapon function obsolete: + OBSOLETE_SYS(CBA_fMyWeapon,{ currentWeapon player }); + (end) + +Author: + Spooner +------------------------------------------- */ +#define OBSOLETE_SYS(OLD_FUNCTION,COMMAND_CODE) \ + OLD_FUNCTION = { \ + WARNING('Obsolete function used: (use: OLD_FUNCTION) in ADDON'); \ + if (isNil "_this") then { call COMMAND_CODE } else { _this call COMMAND_CODE }; \ + } + +/* ------------------------------------------- +Macro: OBSOLETE() + Replace a function, in the current component, that has become obsolete. + + Replace an obsolete OLD_FUNCTION (which will have PREFIX_ prepended) with a simple + COMMAND_CODE, with the intention that anyone using the function should replace it with the simple + command. + + Shows a warning in RPT each time the deprecated function is used. + +Parameters: + OLD_FUNCTION - Name of old function, assuming PREFIX [Identifier for function that does not exist any more] + COMMAND_CODE - Code to replace the old function [Function] + +Example: + (begin example) + // In Arma2, currentWeapon command made the CBA_fMyWeapon function obsolete: + OBSOLETE(fMyWeapon,{ currentWeapon player }); + (end) + +Author: + Spooner +------------------------------------------- */ +#define OBSOLETE(OLD_FUNCTION,COMMAND_CODE) \ + OBSOLETE_SYS(DOUBLES(PREFIX,OLD_FUNCTION),COMMAND_CODE) + +#define BWC_CONFIG(NAME) class NAME { \ + units[] = {}; \ + weapons[] = {}; \ + requiredVersion = REQUIRED_VERSION; \ + requiredAddons[] = {}; \ + version = VERSION; \ +} + +// XEH Specific +#define XEH_CLASS CBA_Extended_EventHandlers +#define XEH_CLASS_BASE DOUBLES(XEH_CLASS,base) +#define XEH_DISABLED class EventHandlers { class XEH_CLASS {}; }; SLX_XEH_DISABLED = 1 +#define XEH_ENABLED class EventHandlers { class XEH_CLASS { EXTENDED_EVENTHANDLERS }; }; SLX_XEH_DISABLED = 0 + +// TODO: These are actually outdated; _Once ? +#define XEH_PRE_INIT QUOTE(call COMPILE_FILE(XEH_PreInit_Once)) +#define XEH_PRE_CINIT QUOTE(call COMPILE_FILE(XEH_PreClientInit_Once)) +#define XEH_PRE_SINIT QUOTE(call COMPILE_FILE(XEH_PreServerInit_Once)) + +#define XEH_POST_INIT QUOTE(call COMPILE_FILE(XEH_PostInit_Once)) +#define XEH_POST_CINIT QUOTE(call COMPILE_FILE(XEH_PostClientInit_Once)) +#define XEH_POST_SINIT QUOTE(call COMPILE_FILE(XEH_PostServerInit_Once)) + +/* ------------------------------------------- +Macro: IS_ADMIN + Check if the local machine is an admin in the multiplayer environment. + + Reports 'true' for logged and voted in admins. + +Parameters: + None + +Example: + (begin example) + // print "true" if player is admin + systemChat str IS_ADMIN; + (end) + +Author: + commy2 +------------------------------------------- */ +#define IS_ADMIN serverCommandAvailable "#kick" + +/* ------------------------------------------- +Macro: IS_ADMIN_LOGGED + Check if the local machine is a logged in admin in the multiplayer environment. + + Reports 'false' if the player was voted to be the admin. + +Parameters: + None + +Example: + (begin example) + // print "true" if player is admin and entered in the server password + systemChat str IS_ADMIN_LOGGED; + (end) + +Author: + commy2 +------------------------------------------- */ +#define IS_ADMIN_LOGGED serverCommandAvailable "#shutdown" + +/* ------------------------------------------- +Macro: FILE_EXISTS + Check if a file exists on machines with interface + + Reports "false" if the file does not exist and throws an error in RPT. + +Parameters: + FILE - Path to the file + +Example: + (begin example) + // print "true" if file exists + systemChat str FILE_EXISTS("\A3\ui_f\data\igui\cfg\cursors\weapon_ca.paa"); + (end) + +Author: + commy2 +------------------------------------------- */ +#define FILE_EXISTS(FILE) (call {\ + private _return = false;\ + isNil {\ + private _control = (uiNamespace getVariable ["RscDisplayMain", displayNull]) ctrlCreate ["RscHTML", -1];\ + if (isNull _control) then {\ + _return = loadFile (FILE) != "";\ + } else {\ + _control htmlLoad (FILE);\ + _return = ctrlHTMLLoaded _control;\ + ctrlDelete _control;\ + };\ + };\ + _return\ +}) diff --git a/arma/addons/main/script_mod.hpp b/arma/addons/main/script_mod.hpp new file mode 100644 index 00000000..3176bbd7 --- /dev/null +++ b/arma/addons/main/script_mod.hpp @@ -0,0 +1,9 @@ +#define MAINPREFIX x +#define PREFIX hemtt + +#include "script_version.hpp" + +#define VERSION MAJOR.MINOR.PATCH +#define VERSION_AR MAJOR,MINOR,PATCH + +#define REQUIRED_VERSION 2.12 diff --git a/arma/addons/main/script_version.hpp b/arma/addons/main/script_version.hpp new file mode 100644 index 00000000..9859d34c --- /dev/null +++ b/arma/addons/main/script_version.hpp @@ -0,0 +1,3 @@ +#define MAJOR 0 +#define MINOR 1 +#define PATCH 0 diff --git a/arma/addons/photoshoot/$PBOPREFIX$ b/arma/addons/photoshoot/$PBOPREFIX$ new file mode 100644 index 00000000..17c5d482 --- /dev/null +++ b/arma/addons/photoshoot/$PBOPREFIX$ @@ -0,0 +1 @@ +x\hemtt\addons\photoshoot diff --git a/arma/addons/photoshoot/chroma.rvmat b/arma/addons/photoshoot/chroma.rvmat new file mode 100644 index 00000000..a27555bd --- /dev/null +++ b/arma/addons/photoshoot/chroma.rvmat @@ -0,0 +1,8 @@ +ambient[]={100,100,100,1}; +diffuse[]={0,0,0,0}; +forcedDiffuse[]={0,0,0,0}; +emmisive[]={100,100,100,1}; +specular[]={0,0,0,0}; +specularPower=0; +PixelShaderID="Normal"; +VertexShaderID="Basic"; diff --git a/arma/addons/photoshoot/config.cpp b/arma/addons/photoshoot/config.cpp new file mode 100644 index 00000000..5fea0f6b --- /dev/null +++ b/arma/addons/photoshoot/config.cpp @@ -0,0 +1,43 @@ +#include "script_component.hpp" + +class CfgPatches { + class ADDON { + name = QUOTE(COMPONENT); + units[] = {}; + weapons[] = {}; + requiredVersion = REQUIRED_VERSION; + requiredAddons[] = { + "A3_Data_F_Mod_Loadorder" + }; + VERSION_CONFIG; + }; +}; + +class CfgFaces { + class Default; + class Man_A3: Default { + class Default {}; + class WhiteHead_01: Default {}; + class HEMTTPhotoshoot: WhiteHead_01 { + author = "HEMTT"; + displayName = "HEMTT Photoshoot"; + texture = "#(argb,8,8,3)color(1,0,1,1,ca)"; + textureHL = "#(argb,8,8,3)color(1,0,1,1,ca)"; + textureHL2 = "#(argb,8,8,3)color(1,0,1,1,ca)"; + material = QPATHTOF(chroma.rvmat); + materialHL = QPATHTOF(chroma.rvmat); + materialHL2 = QPATHTOF(chroma.rvmat); + }; + }; +}; + +class CfgIdentities { + class HEMTTPhotoshoot { + face = "HEMTTPhotoshoot"; + glasses = "None"; + name = "HEMTTPhotoshoot"; + nameSound = "Kerry"; + pitch = 1.0; + speaker = "Male01ENG"; + }; +}; diff --git a/arma/addons/photoshoot/script_component.hpp b/arma/addons/photoshoot/script_component.hpp new file mode 100644 index 00000000..e4dde0da --- /dev/null +++ b/arma/addons/photoshoot/script_component.hpp @@ -0,0 +1,4 @@ +#define COMPONENT photoshoot + +#include "..\main\script_mod.hpp" +#include "..\main\script_macros.hpp" diff --git a/arma/hemtt_comm_x64.dll b/arma/hemtt_comm_x64.dll new file mode 100644 index 00000000..6fc95626 Binary files /dev/null and b/arma/hemtt_comm_x64.dll differ diff --git a/arma/src/conn.rs b/arma/src/conn.rs new file mode 100644 index 00000000..3e12732c --- /dev/null +++ b/arma/src/conn.rs @@ -0,0 +1,27 @@ +use std::{mem::MaybeUninit, sync::mpsc::Sender}; + +use hemtt_common::arma::control::fromarma::Message; + +pub struct Conn(); + +static mut SINGLETON: MaybeUninit> = MaybeUninit::uninit(); +static mut INIT: bool = false; + +impl Conn { + /// Gets a reference to the sender + /// + /// # Panics + /// + /// Panics if the sender has not been set + pub fn get() -> Sender { + unsafe { SINGLETON.assume_init_ref().clone() } + } + + /// Store the sender + pub fn set(sender: Sender) { + unsafe { + SINGLETON = MaybeUninit::new(sender); + INIT = true; + } + } +} diff --git a/arma/src/lib.rs b/arma/src/lib.rs new file mode 100644 index 00000000..8df80805 --- /dev/null +++ b/arma/src/lib.rs @@ -0,0 +1,89 @@ +use std::io::{Read, Write}; + +use arma_rs::{arma, Extension}; +use conn::Conn; +use hemtt_common::arma::control::{ + fromarma::{self, Control, Message}, + toarma, +}; +use interprocess::local_socket::{prelude::*, GenericNamespaced, Stream}; + +mod conn; +mod photoshoot; + +#[arma] +fn init() -> Extension { + let ext = Extension::build() + .command("mission", mission) + .group("photoshoot", photoshoot::group()) + .finish(); + let ctx = ext.context(); + let (send, recv) = std::sync::mpsc::channel::(); + std::thread::spawn(move || { + Conn::set(send); + let mut socket = + Stream::connect("hemtt_arma".to_ns_name::().unwrap()).unwrap(); + socket.set_nonblocking(true).unwrap(); + loop { + let mut len_buf = [0u8; 4]; + if socket.read_exact(&mut len_buf).is_ok() + && !len_buf.is_empty() + && len_buf != [255u8; 4] + { + let len = u32::from_le_bytes(len_buf); + println!("Receiving: {}", len); + let mut buf = vec![0u8; len as usize]; + socket.read_exact(&mut buf).unwrap(); + let buf = String::from_utf8(buf).unwrap(); + let message: toarma::Message = serde_json::from_str(&buf).unwrap(); + match message { + toarma::Message::Control(control) => match control { + toarma::Control::Exit => { + std::process::exit(0); + } + }, + toarma::Message::Photoshoot(photoshoot) => match photoshoot { + toarma::Photoshoot::Weapon(weapon) => { + println!("Weapon: {}", weapon); + ctx.callback_data("hemtt_photoshoot", "weapon", weapon.clone()) + .unwrap(); + } + toarma::Photoshoot::Preview(class) => { + println!("Preview: {}", class); + ctx.callback_data("hemtt_photoshoot", "preview_add", class.clone()) + .unwrap(); + } + toarma::Photoshoot::PreviewRun => { + println!("PreviewRun"); + ctx.callback_null("hemtt_photoshoot", "preview_run") + .unwrap(); + } + toarma::Photoshoot::Done => { + println!("Done"); + ctx.callback_null("hemtt_photoshoot", "done").unwrap(); + } + }, + } + } + if let Ok(message) = recv.recv_timeout(std::time::Duration::from_millis(100)) { + crate::send(message, &mut socket); + } + } + }); + ext +} + +fn mission(mission: String) { + Conn::get() + .send(Message::Control(Control::Mission(mission))) + .unwrap(); +} + +fn send(message: fromarma::Message, socket: &mut Stream) { + let message = serde_json::to_string(&message).unwrap(); + socket + .write_all(&u32::to_le_bytes(message.len() as u32)) + .unwrap(); + socket.write_all(message.as_bytes()).unwrap(); + socket.flush().unwrap(); +} diff --git a/arma/src/photoshoot.rs b/arma/src/photoshoot.rs new file mode 100644 index 00000000..e1556b33 --- /dev/null +++ b/arma/src/photoshoot.rs @@ -0,0 +1,29 @@ +use arma_rs::Group; +use hemtt_common::arma::control::fromarma::{Message, Photoshoot}; + +use crate::conn::Conn; + +pub fn group() -> Group { + Group::new() + .command("ready", ready) + .command("weapon", weapon) + .command("previews", previews) +} + +fn ready() { + Conn::get() + .send(Message::Photoshoot(Photoshoot::Ready)) + .unwrap(); +} + +fn weapon(weapon: String) { + Conn::get() + .send(Message::Photoshoot(Photoshoot::Weapon(weapon))) + .unwrap(); +} + +fn previews() { + Conn::get() + .send(Message::Photoshoot(Photoshoot::Previews)) + .unwrap(); +} diff --git a/bin/Cargo.toml b/bin/Cargo.toml index 591e9926..f1a59b48 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -38,16 +38,20 @@ dirs = { workspace = true } fs_extra = "1.3.0" git2 = { workspace = true } glob = "0.3.1" +image = "0.25.5" indicatif = "0.17.9" +interprocess = { workspace = true } num_cpus = "1.16.0" paste = { workspace = true } rayon = "1.10.0" regex = { workspace = true } reqwest = { version = "0.12.9", features = ["blocking", "json"] } rhai = "1.20.0" +rust-embed = "8.5.0" semver = "1.0.23" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +state = "0.6.0" supports-hyperlinks = { workspace = true } tabled = { workspace = true } terminal-link = { workspace = true } diff --git a/bin/dist/profile/@hemtt/addons/hemtt_main.pbo b/bin/dist/profile/@hemtt/addons/hemtt_main.pbo new file mode 100644 index 00000000..d3bf0cf7 Binary files /dev/null and b/bin/dist/profile/@hemtt/addons/hemtt_main.pbo differ diff --git a/bin/dist/profile/@hemtt/addons/hemtt_main.pbo.hemtt_0.0.0.bisign b/bin/dist/profile/@hemtt/addons/hemtt_main.pbo.hemtt_0.0.0.bisign new file mode 100644 index 00000000..0392277d Binary files /dev/null and b/bin/dist/profile/@hemtt/addons/hemtt_main.pbo.hemtt_0.0.0.bisign differ diff --git a/bin/dist/profile/@hemtt/addons/hemtt_photoshoot.pbo b/bin/dist/profile/@hemtt/addons/hemtt_photoshoot.pbo new file mode 100644 index 00000000..a5c5723f Binary files /dev/null and b/bin/dist/profile/@hemtt/addons/hemtt_photoshoot.pbo differ diff --git a/bin/dist/profile/@hemtt/addons/hemtt_photoshoot.pbo.hemtt_0.0.0.bisign b/bin/dist/profile/@hemtt/addons/hemtt_photoshoot.pbo.hemtt_0.0.0.bisign new file mode 100644 index 00000000..d96dcc26 Binary files /dev/null and b/bin/dist/profile/@hemtt/addons/hemtt_photoshoot.pbo.hemtt_0.0.0.bisign differ diff --git a/bin/dist/profile/@hemtt/hemtt_comm_x64.dll b/bin/dist/profile/@hemtt/hemtt_comm_x64.dll new file mode 100644 index 00000000..6fc95626 Binary files /dev/null and b/bin/dist/profile/@hemtt/hemtt_comm_x64.dll differ diff --git a/bin/dist/profile/@hemtt/keys/hemtt_0.0.0.bikey b/bin/dist/profile/@hemtt/keys/hemtt_0.0.0.bikey new file mode 100644 index 00000000..454ee4ae Binary files /dev/null and b/bin/dist/profile/@hemtt/keys/hemtt_0.0.0.bikey differ diff --git a/bin/dist/profile/Users/hemtt/hemtt.3den.Arma3Profile b/bin/dist/profile/Users/hemtt/hemtt.3den.Arma3Profile new file mode 100644 index 00000000..7f08f49e --- /dev/null +++ b/bin/dist/profile/Users/hemtt/hemtt.3den.Arma3Profile @@ -0,0 +1 @@ +version=1; diff --git a/bin/dist/profile/Users/hemtt/hemtt.Arma3Profile b/bin/dist/profile/Users/hemtt/hemtt.Arma3Profile new file mode 100644 index 00000000..2d93c1b4 --- /dev/null +++ b/bin/dist/profile/Users/hemtt/hemtt.Arma3Profile @@ -0,0 +1,565 @@ +version=2; +blood=1; +singleVoice=0; +class Identity +{ + face="HEMTTPhotoshoot"; + glasses="None"; + speaker="Male01_F"; + unitType=0; + squad=""; + unitId=-1; + pitch=1; +}; +tripleHead=0; +anisoFilter=16; +textureQuality=4; +shadowQuality=5; +class DifficultyPresets +{ + class CustomDifficulty + { + class Options + { + groupIndicators=0; + friendlyTags=0; + enemyTags=0; + detectedMines=0; + commands=0; + waypoints=0; + weaponInfo=2; + stanceIndicator=0; + reducedDamage=0; + staminaBar=0; + weaponCrosshair=0; + visionAid=0; + thirdPersonView=0; + cameraShake=1; + scoreTable=0; + deathMessages=0; + vonID=0; + mapContentFriendly=0; + mapContentEnemy=0; + mapContentMines=0; + autoReport=0; + multipleSaves=0; + tacticalPing=0; + }; + aiLevelPreset="AILevelMedium"; + }; + class CustomAILevel + { + skillAI=0.5; + precisionAI=0.5; + }; +}; +adaptiveCrosshair=1; +RTDEnabled=0; +RTDFullHUD=1; +RTDRoughLanding=1; +RTDWindDynamic=0; +RTDAutoTrim=1; +RTDStressDamage=0; +showTitles=1; +useImperialSystem=0; +vehicleFreelook=0; +useIncapacitatedState=1; +tutorialHints=1; +streamFriendlyUI=0; +instructorFigure=1; +showRadio=1; +customScheme=1; +controller="Default"; +sensitivity="Medium"; +vibrations=1; +class ControllerSchemes +{ +}; +keySwimUp[]={45}; +keySwimDown[]={44}; +keyAdjustUp[]={487784465}; +keyAdjustDown[]={487784479}; +keyAdjustLeft[]={487784478}; +keyAdjustRight[]={487784480}; +keyMoveForward[]={17,200}; +keyMoveBack[]={31,208}; +keyTurnLeft[]={30,203}; +keyTurnRight[]={32,205}; +keyMoveUp[]={45}; +keyMoveDown[]={44}; +keyMoveFastForward[]={}; +keyMoveSlowForward[]={}; +keyMoveLeft[]={}; +keyMoveRight[]={}; +keyEvasiveLeft[]={16}; +keyEvasiveRight[]={18}; +keyStand[]={}; +keyCrouch[]={}; +keyProne[]={}; +keyLeanLeft[]={16,524291}; +keyLeanRight[]={18,524297}; +keyLeanLeftToggle[]={272}; +keyLeanRightToggle[]={274}; +keyWalkRunToggle[]={487784494}; +keyWalkRunTemp[]={}; +keyTactToggle[]={302}; +keyTactTemp[]={}; +keyTactShort[]={65536}; +keyNextWeapon[]={33}; +keyPrevWeapon[]={}; +keyHandgun[]={39}; +keySwitchWeapon[]={-1659699161}; +keySwitchPrimary[]={2}; +keySwitchHandgun[]={3}; +keySwitchSecondary[]={4}; +keySwitchWeaponGrp1[]={2}; +keySwitchWeaponGrp2[]={3}; +keySwitchWeaponGrp3[]={4}; +keySwitchWeaponGrp4[]={5}; +keyFire[]={487653376}; +keyDefaultAction[]={65536}; +keyActionInMap[]={65536}; +keyThrow[]={34}; +keyCycleThrownItems[]={487784482}; +keyReloadMagazine[]={19}; +keyLockTargets[]={}; +keyLockEmptyTargets[]={}; +keyLockTarget[]={20}; +keyRevealTarget[]={20}; +keyTacticalPing[]={705888276}; +keyZoomTemp[]={65537}; +keyTempRaiseWeapon[]={}; +keyToggleRaiseWeapon[]={285}; +keyPrevAction[]={1048580}; +keyNextAction[]={1048581}; +keyAction[]={57,28}; +keyActionContext[]={65538}; +keyActionFreeLook[]={65536}; +keyHeadlights[]={38}; +keyNightVision[]={49}; +keyTransportNightVision[]={-1659699151}; +keyBinocular[]={48}; +keyCompass[]={37}; +keyCompassToggle[]={293}; +keyWatch[]={24}; +keyWatchToggle[]={280}; +keyMiniMap[]={}; +keyMiniMapToggle[]={-1659699150}; +keyShowMap[]={50}; +keyHideMap[]={50}; +keyUavView[]={15}; +keyUavViewToggle[]={271}; +keyDeployWeaponAuto[]={46}; +keyDeployWeaponManual[]={}; +keyHelp[]={35}; +keyTimeInc[]={13}; +keyTimeDec[]={12}; +keyOptics[]={65665,82}; +keyOpticsTemp[]={65665,82}; +keyPilotCamera[]={487653505}; +keyOpticsMode[]={487653505,181}; +keyPersonView[]={156}; +keyTacticalView[]={83}; +keyZoomIn[]={78,65540}; +keyZoomInToggle[]={334}; +keyZoomOut[]={74,65539}; +keyZoomOutToggle[]={330}; +keyZoomContIn[]={524299}; +keyZoomContOut[]={524293}; +keyZeroingUp[]={201,487587845}; +keyZeroingDown[]={209,487587844}; +keyGunElevUp[]={201}; +keyGunElevDown[]={209}; +keyGunElevSlow[]={42,54}; +keyGunElevAuto[]={20}; +keyLookAround[]={56}; +keyLookAroundToggle[]={312,55}; +keyLookLeftDown[]={79}; +keyLookDown[]={80}; +keyLookRightDown[]={81}; +keyLookLeft[]={75}; +keyLookCenter[]={76}; +keyLookRight[]={77}; +keyLookLeftUp[]={71}; +keyLookUp[]={72}; +keyLookRightUp[]={73}; +keyLookLeftCont[]={524289}; +keyLookRightCont[]={524295}; +keyLookDownCont[]={524288}; +keyLookUpCont[]={524294}; +keyLookShiftLeftCont[]={524291}; +keyLookShiftRightCont[]={524297}; +keyLookShiftUpCont[]={}; +keyLookShiftDownCont[]={}; +keyLookShiftForwardCont[]={}; +keyLookShiftBackCont[]={}; +keyLookRollLeftCont[]={524290}; +keyLookRollRightCont[]={524296}; +keyLookShiftLeft[]={487784523}; +keyLookShiftRight[]={487784525}; +keyLookShiftUp[]={487784649}; +keyLookShiftDown[]={487784657}; +keyLookShiftForward[]={487784520}; +keyLookShiftBack[]={487784528}; +keyLookRollLeft[]={487784519}; +keyLookRollRight[]={487784521}; +keyLookShiftCenter[]={487784524}; +keyFreeHeadMove[]={}; +keyVehLockTurretView[]={487784468}; +keyPrevChannel[]={51}; +keyNextChannel[]={52}; +keyChat[]={53}; +keyVoiceOverNet[]={}; +keyPushToTalk[]={58}; +keyPushToTalkAll[]={}; +keyPushToTalkSide[]={}; +keyPushToTalkCommand[]={}; +keyPushToTalkGroup[]={}; +keyPushToTalkVehicle[]={}; +keyPushToTalkDirect[]={}; +keyCommandingMenuSelect1[]={2}; +keyCommandingMenuSelect2[]={3}; +keyCommandingMenuSelect3[]={4}; +keyCommandingMenuSelect4[]={5}; +keyCommandingMenuSelect5[]={6}; +keyCommandingMenuSelect6[]={7}; +keyCommandingMenuSelect7[]={8}; +keyCommandingMenuSelect8[]={9}; +keyCommandingMenuSelect9[]={10}; +keyCommandingMenuSelect0[]={11}; +keyCommandingMenu1[]={}; +keyCommandingMenu2[]={}; +keyCommandingMenu3[]={}; +keyCommandingMenu4[]={}; +keyCommandingMenu5[]={}; +keyCommandingMenu6[]={}; +keyCommandingMenu7[]={}; +keyCommandingMenu8[]={}; +keyCommandingMenu9[]={}; +keyCommandingMenu0[]={11}; +keySelectGroupUnit1[]={59}; +keySelectGroupUnit2[]={60}; +keySelectGroupUnit3[]={61}; +keySelectGroupUnit4[]={62}; +keySelectGroupUnit5[]={63}; +keySelectGroupUnit6[]={64}; +keySelectGroupUnit7[]={65}; +keySelectGroupUnit8[]={66}; +keySelectGroupUnit9[]={67}; +keySelectGroupUnit0[]={68}; +keyGroupPagePrev[]={87}; +keyGroupPageNext[]={88}; +keySelectTeamRed[]={705888315,907214907}; +keySelectTeamGreen[]={705888316,907214908}; +keySelectTeamBlue[]={705888317,907214909}; +keySelectTeamYellow[]={705888318,907214910}; +keySelectTeamWhite[]={705888319,907214911}; +keySetTeamRed[]={487784507,-1659699141}; +keySetTeamGreen[]={487784508,-1659699140}; +keySetTeamBlue[]={487784509,-1659699139}; +keySetTeamYellow[]={487784510,-1659699138}; +keySetTeamWhite[]={487784511,-1659699137}; +keyNetworkStats[]={25}; +keyNetworkPlayers[]={-1659699175}; +keySelectAll[]={41}; +keyTurbo[]={42}; +keyTurboToggle[]={}; +keyVehicleTurbo[]={42}; +keyWalk[]={}; +keyEngineToggle[]={}; +keyActiveSensorsToggle[]={487784467}; +keyListLeftVehicleDisplay[]={26}; +keyListRightVehicleDisplay[]={27}; +keyListPrevLeftVehicleDisplay[]={}; +keyListPrevRightVehicleDisplay[]={}; +keyCloseLeftVehicleDisplay[]={}; +keyCloseRightVehicleDisplay[]={}; +keyNextModeLeftVehicleDisplay[]={-1659699174}; +keyNextModeRightVehicleDisplay[]={-1659699173}; +keyHoldBreath[]={42}; +keySalute[]={43}; +keySitDown[]={40}; +keySurrender[]={}; +keyGetOver[]={47}; +keyAimUp[]={1048578}; +keyAimDown[]={1048579}; +keyAimLeft[]={1048576}; +keyAimRight[]={1048577}; +keyAimHeadUp[]={1048578}; +keyAimHeadDown[]={1048579}; +keyAimHeadLeft[]={1048576}; +keyAimHeadRight[]={1048577}; +keyIngamePause[]={1}; +keyMenuSelect[]={}; +keyNavigateMenu[]={14}; +keyCloseContext[]={65665}; +keyForceCommandingMode[]={}; +keySwitchCommand[]={487784505,221}; +keyHeliUp[]={42}; +keyHeliDown[]={44}; +keyHeliLeft[]={1048576}; +keyHeliRight[]={1048577}; +keyAirBankLeft[]={30,203}; +keyAirBankRight[]={32,205}; +keyHeliRudderLeft[]={16,211}; +keyHeliRudderRight[]={18,207}; +keyHeliForward[]={17,200,1048579}; +keyHeliBack[]={31,208,1048578}; +keyHeliFastForward[]={}; +keyAutoHover[]={45}; +keyAutoHoverCancel[]={45}; +keyVTOLVectoring[]={487784493}; +keyVTOLVectoringCancel[]={487784493}; +keyHeliThrottlePos[]={}; +keyHeliThrottleNeg[]={}; +keyHeliBrakes[]={}; +keyAirPlaneBrake[]={45}; +keyHeliCyclicForward[]={17,200,1048579}; +keyHeliCyclicBack[]={31,208,1048578}; +keyHeliCyclicLeft[]={30,203}; +keyHeliCyclicRight[]={32,205}; +keyHeliCollectiveRaise[]={42}; +keyHeliCollectiveLower[]={44}; +keyHeliCollectiveRaiseCont[]={}; +keyHeliCollectiveLowerCont[]={}; +keyHeliWheelsBrake[]={-1659699183}; +keyHeliTrimLeft[]={-1659699125}; +keyHeliTrimRight[]={-1659699123}; +keyHeliTrimForward[]={-1659699128}; +keyHeliTrimBackward[]={-1659699120}; +keyHeliTrimRudderLeft[]={-1659699121}; +keyHeliTrimRudderRight[]={-1659699119}; +keyHeliRopeAction[]={48}; +keyHeliSlingLoadManager[]={-1659699152}; +keySubmarineUp[]={45}; +keySubmarineDown[]={44}; +keySubmarineLeft[]={30,203,1048576}; +keySubmarineRight[]={32,205,1048577}; +keySubmarineForward[]={17,200}; +keySubmarineBack[]={31,208}; +keySubmarineCyclicForward[]={1048578}; +keySubmarineCyclicBack[]={1048579}; +keySeagullUp[]={16,1048579}; +keySeagullDown[]={44,1048578}; +keySeagullForward[]={17,200}; +keySeagullBack[]={31,208}; +keySeagullFastForward[]={18}; +keyCarLeft[]={30,203}; +keyCarRight[]={32,205}; +keyCarLinearLeft[]={}; +keyCarLinearRight[]={}; +keyCarWheelLeft[]={1048576}; +keyCarWheelRight[]={1048577}; +keyCarForward[]={17,200}; +keyCarBack[]={31,208}; +keyCarFastForward[]={705888273}; +keyCarSlowForward[]={487784465}; +keyCarHandBrake[]={45}; +keyCarAimUp[]={1048578}; +keyCarAimDown[]={1048579}; +keyCarAimLeft[]={1048576}; +keyCarAimRight[]={1048577}; +keyCommandLeft[]={30,203}; +keyCommandRight[]={32,205}; +keyCommandForward[]={17,200}; +keyCommandBack[]={31,208}; +keyCommandFast[]={286457898}; +keyCommandSlow[]={286457885,487784465}; +keySwitchGunnerWeapon[]={487784481}; +keyVehLockTargets[]={19}; +keyVehLockEmptyTargets[]={}; +keySwapGunner[]={}; +keyHeliManualFire[]={40}; +keyTurnIn[]={487784464}; +keyTurnOut[]={487784466}; +keyCancelAction[]={}; +keyCommandWatch[]={56}; +keyTeamSwitch[]={22}; +keyTeamSwitchPrev[]={}; +keyTeamSwitchNext[]={}; +keyGear[]={23}; +keyCuratorInterface[]={21}; +keyCuratorDelete[]={211}; +keyCuratorDestroy[]={207}; +keyCuratorMoveCamTo[]={33}; +keyCuratorLockCameraTo[]={487784481}; +keyCuratorLevelObject[]={45}; +keyCuratorRotateMod[]={42}; +keyCuratorGroupMod[]={29}; +keyCuratorMultipleMod[]={29}; +keyCuratorContentWaypoint[]={56}; +keyCuratorMoveY[]={56}; +keyCuratorGetOut[]={34}; +keyCuratorCollapseParent[]={46}; +keyCuratorNightvision[]={49}; +keyCuratorPersonView[]={156}; +keyCuratorPingView[]={57}; +keyCuratorToggleInterface[]={14}; +keyCuratorToggleEdit[]={18}; +keyCuratorToggleCreate[]={19}; +keyCuratorMapTextures[]={20}; +keyCuratorCompass[]={37}; +keyCuratorWatch[]={24}; +keyEditorCameraMoveForward[]={17}; +keyEditorCameraMoveBackward[]={31}; +keyEditorCameraMoveLeft[]={30}; +keyEditorCameraMoveRight[]={32}; +keyEditorCameraMoveUp[]={16}; +keyEditorCameraMoveDown[]={44}; +keyEditorCameraMoveTurbo[]={42,54}; +keyEditorCameraLookLeft[]={75}; +keyEditorCameraLookRight[]={77}; +keyEditorCameraLookUp[]={72}; +keyEditorCameraLookDown[]={80}; +keyCameraMoveForward[]={17}; +keyCameraMoveBackward[]={31}; +keyCameraMoveLeft[]={30}; +keyCameraMoveRight[]={32}; +keyCameraMoveUp[]={16}; +keyCameraMoveDown[]={44}; +keyCameraMoveTurbo1[]={42,54}; +keyCameraMoveTurbo2[]={56,184}; +keyCameraZoomIn[]={78}; +keyCameraZoomOut[]={74}; +keyCameraLookUp[]={72}; +keyCameraLookDown[]={80}; +keyCameraLookLeft[]={75}; +keyCameraLookRight[]={77}; +keyCameraReset[]={76}; +keyCameraTarget[]={33}; +keyCameraVisionMode[]={49}; +keyCameraFlashlight[]={38}; +keyCameraInterface[]={14}; +keyGetOut[]={47}; +keyEject[]={303}; +keyLandGear[]={34}; +keyLandGearUp[]={34}; +keyFlapsDown[]={487587845,-1659699163}; +keyFlapsUp[]={487587844,-1659699162}; +keyLaunchCM[]={46}; +keyNextCM[]={487784494}; +keyHelicopterTrimOn[]={-1659699124}; +keyHelicopterTrimOff[]={-1659699117}; +keyTurretElevationUp[]={18}; +keyTurretElevationDown[]={16}; +keyCopyVersion[]={487784501}; +keyOpenDlcScreen[]={705888281}; +keyBuldSwitchCamera[]={210}; +keyBuldFreeLook[]={76}; +keyBuldSelect[]={57}; +keyBuldResetCamera[]={82}; +keyBuldMagnetizePoints[]={63}; +keyBuldMagnetizePlanes[]={64}; +keyBuldMagnetizeYFixed[]={65}; +keyBuldTerrainRaise1m[]={24}; +keyBuldTerrainRaise10cm[]={22}; +keyBuldTerrainLower1m[]={38}; +keyBuldTerrainLower10cm[]={36}; +keyBuldTerrainRaise5m[]={25}; +keyBuldTerrainRaise50cm[]={23}; +keyBuldTerrainLower5m[]={39}; +keyBuldTerrainLower50cm[]={37}; +keyBuldTerrainShowNode[]={35}; +keyBuldSelectionType[]={31}; +keyBuldLeft[]={203}; +keyBuldRight[]={205}; +keyBuldForward[]={200}; +keyBuldBack[]={208}; +keyBuldMoveLeft[]={1048576}; +keyBuldMoveRight[]={1048577}; +keyBuldMoveForward[]={1048578}; +keyBuldMoveBack[]={1048579}; +keyBuldTurbo[]={42}; +keyBuldUp[]={201,16}; +keyBuldDown[]={209,44}; +keyBuldLookLeft[]={75}; +keyBuldLookRight[]={77}; +keyBuldLookUp[]={72}; +keyBuldLookDown[]={80}; +keyBuldZoomIn[]={78}; +keyBuldZoomOut[]={74}; +keyBuldTextureInfo[]={20}; +keyBuldBrushRatio[]={48}; +keyBuldBrushStrength[]={50}; +keyBuldBrushSmooth[]={42}; +keyBuldBrushRandomize[]={19}; +keyBuldBrushSetHeight[]={46}; +keyBuldBrushOuter[]={49}; +keyBuldUndo[]={487784492}; +keyBuldRedo[]={487784467}; +keyBuldCreateObj[]={47}; +keyBuldDuplicateSel[]={46}; +keyBuldRemoveSel[]={32}; +keyBuldRotateSelX[]={45}; +keyBuldRotateSelZ[]={44}; +keyBuldScaleSel[]={18}; +keyBuldElevateSel[]={17}; +keyBuldKeepAbsoluteElevationSel[]={50}; +keyBuldClearAllElevationLocks[]={705888306,907214898}; +keyDiary[]={292}; +keyTasks[]={36}; +keyTasksToggle[]={}; +keyUser1[]={}; +keyUser2[]={}; +keyUser3[]={}; +keyUser4[]={}; +keyUser5[]={}; +keyUser6[]={}; +keyUser7[]={}; +keyUser8[]={}; +keyUser9[]={}; +keyUser10[]={}; +keyUser11[]={}; +keyUser12[]={}; +keyUser13[]={}; +keyUser14[]={}; +keyUser15[]={}; +keyUser16[]={}; +keyUser17[]={}; +keyUser18[]={}; +keyUser19[]={}; +keyUser20[]={}; +revMouse=0; +mouseSmoothing=5; +mouseAcceleration=0; +class MainMap +{ + class Compass + { + inBack=0; + position[]={-0.047599997,-0.044625003,0.2}; + positionBack[]={0.0070000053,0,0.1}; + }; +}; +bootCampWarning=1; +keyDBUG_OpenConsole[]={}; +mouseSensitivityX=1; +mouseSensitivityY=1; +class JoysticksList {}; +headBob=1; +floatingZoneArea=0; +sceneComplexity=1800000; +shadowZDistance=50; +viewDistance=500.00006; +preferredObjectViewDistance=500.00006; +pipViewDistance=500; +terrainGrid=3.125; +volumeCD=5; +volumeFX=5; +volumeSpeech=5; +volumeVoN=10; +vonRecThreshold=0.029999999; +volumeMapDucking=1; +volumeUI=1; +gamma=0.54411757; +ppBrightness=1; +ppContrast=1; +ppSaturation=1; +brightness=1; +fovTop=0.75; +fovLeft=1.3333334; +uiTopLeftX=0.29374999; +uiTopLeftY=0.22499999; +uiBottomRightX=0.70625001; +uiBottomRightY=0.77499998; +IGUIScale=0.55000001; diff --git a/bin/dist/profile/Users/hemtt/hemtt.vars.Arma3Profile b/bin/dist/profile/Users/hemtt/hemtt.vars.Arma3Profile new file mode 100644 index 00000000..97e74b08 Binary files /dev/null and b/bin/dist/profile/Users/hemtt/hemtt.vars.Arma3Profile differ diff --git a/bin/dist/profile/arma3.cfg b/bin/dist/profile/arma3.cfg new file mode 100644 index 00000000..bd9cf534 --- /dev/null +++ b/bin/dist/profile/arma3.cfg @@ -0,0 +1,34 @@ +language="English"; +winX=16; +winY=32; +winWidth=1920; +winHeight=1080; +winDefWidth=1920; +winDefHeight=1080; +fullScreenWidth=1920; +fullScreenHeight=1080; +renderWidth=1920; +renderHeight=1080; +multiSampleCount=8; +multiSampleQuality=0; +particlesQuality=2; +GPU_MaxFramesAhead=1000; +GPU_DetectedFramesAhead=3; +HDRPrecision=16; +vsync=1; +AToC=15; +cloudsQuality=4; +waterSSReflectionsQuality=0; +pipQuality=4; +dynamicLightsQuality=4; +PPAA=0; +ppSSAO=6; +ppCaustics=0; +tripleBuffering=0; +displayMode=1; +ppHaze=2; +ppBloom=0; +ppRotBlur=0; +ppRadialBlur=0; +ppDOF=0; +ppSharpen=0.5; diff --git a/bin/dist/profile/autotest/photoshoot.VR/functions/fnc_uniforms.sqf b/bin/dist/profile/autotest/photoshoot.VR/functions/fnc_uniforms.sqf new file mode 100644 index 00000000..f18d5d36 --- /dev/null +++ b/bin/dist/profile/autotest/photoshoot.VR/functions/fnc_uniforms.sqf @@ -0,0 +1,35 @@ +params [ + ["_uniforms", [], [[]]] +]; + +private _delay = 0.1; + +sleep 1; + +ps_cam cameraEffect ["INTERNAL", "BACK"]; +ps_cam camSetDir vectorDir (ps_camLocations get "uniform"); +ps_cam camSetPos getPos (ps_camLocations get "uniform"); +ps_cam camCommit 0; + +sleep _delay; + +// Preload assets +{ + model_clothing setUnitLoadout "C_Soldier_VR_F"; + model_clothing forceAddUniform _x; + sleep _delay; +} forEach _uniforms; + +sleep 3; + +// Take screenshots +{ + model_clothing setUnitLoadout "C_Soldier_VR_F"; + model_clothing forceAddUniform _x; + sleep _delay; + screenshot format ["%1.png", _x]; + sleep _delay; + "hemtt_comm" callExtension ["photoshoot:uniform", [_x]]; +} forEach _uniforms; + +endMission "END1"; diff --git a/bin/dist/profile/autotest/photoshoot.VR/init.sqf b/bin/dist/profile/autotest/photoshoot.VR/init.sqf new file mode 100644 index 00000000..860e4dae --- /dev/null +++ b/bin/dist/profile/autotest/photoshoot.VR/init.sqf @@ -0,0 +1,31 @@ +ps_preview = []; + +addMissionEventHandler ["ExtensionCallback", { + params ["_name", "_function", "_data"]; + diag_log format ["%1: %2", _name, _function]; + if (_name isEqualTo "hemtt_photoshoot") then { + switch (_function) do { + case "preview_add": { + diag_log format ["Preview: %1", _data]; + ps_preview pushBack _data; + }; + case "preview_run": { + diag_log "Preview: Run"; + 0 spawn { + diag_log "Preview: Start"; + diag_log format ["Preview: %1", count ps_preview]; + [nil, "all", [], [], [], ps_preview] call BIS_fnc_exportEditorPreviews; + sleep 2; + diag_log "Preview: Done"; + "hemtt_comm" callExtension ["photoshoot:previews", []]; + }; + }; + case "done": { + endMission "END1"; + }; + }; + }; +}]; + +diag_log "Photoshoot: Ready"; +diag_log format ["response: %1", "hemtt_comm" callExtension ["photoshoot:ready", []]]; diff --git a/bin/dist/profile/autotest/photoshoot.VR/mission.sqm b/bin/dist/profile/autotest/photoshoot.VR/mission.sqm new file mode 100644 index 00000000..d933d698 --- /dev/null +++ b/bin/dist/profile/autotest/photoshoot.VR/mission.sqm @@ -0,0 +1,267 @@ +version=54; +class EditorData +{ + moveGridStep=1; + angleGridStep=0.2617994; + scaleGridStep=1; + autoGroupingDist=10; + toggles=1; + class ItemIDProvider + { + nextID=23; + }; + class Camera + { + pos[]={392.44739,17.170723,193.28291}; + dir[]={-0.94935566,-0.18725735,0.25230667}; + up[]={-0.18097511,0.98231089,0.04809707}; + aside[]={0.25685012,0,0.96645135}; + }; +}; +binarizationWanted=0; +sourceName="photoshoot"; +addons[]= +{ + "A3_Misc_F_Helpers", + "A3_Characters_F", + "A3_Characters_F_Exp_Civil", + "A3_Weapons_F" +}; +class AddonsMetaData +{ + class List + { + items=4; + class Item0 + { + className="A3_Misc_F"; + name="Arma 3 - 3D Aids and Helpers"; + author="Bohemia Interactive"; + url="https://www.arma3.com"; + }; + class Item1 + { + className="A3_Characters_F"; + name="Arma 3 Alpha - Characters and Clothing"; + author="Bohemia Interactive"; + url="https://www.arma3.com"; + }; + class Item2 + { + className="A3_Characters_F_Exp"; + name="Arma 3 Apex - Characters and Clothing"; + author="Bohemia Interactive"; + url="https://www.arma3.com"; + }; + class Item3 + { + className="A3_Weapons_F"; + name="Arma 3 Alpha - Weapons and Accessories"; + author="Bohemia Interactive"; + url="https://www.arma3.com"; + }; + }; +}; +randomSeed=12862063; +class ScenarioData +{ + author="Brett"; +}; +class Mission +{ + class Intel + { + startWeather=1; + startWind=0; + startWaves=0.1; + forecastWeather=0; + forecastWind=0; + forecastWaves=0.1; + forecastLightnings=0.1; + rainForced=1; + lightningsForced=1; + wavesForced=1; + windForced=1; + year=2035; + month=1; + day=1; + hour=12; + minute=0; + startFogDecay=0.014; + forecastFogDecay=0.014; + }; + class Entities + { + items=3; + class Item0 + { + dataType="Object"; + class PositionInfo + { + position[]={100,15,100}; + }; + side="Empty"; + class Attributes + { + }; + id=0; + type="UserTexture10m_F"; + atlOffset=10; + class CustomAttributes + { + class Attribute0 + { + property="ObjectTextureCustom0"; + expression="_this setObjectTextureGlobal [0,_value]"; + class Value + { + class data + { + singleType="STRING"; + value="#(rgb,8,8,3)color(1,0,1,1)"; + }; + }; + }; + class Attribute1 + { + property="ObjectMaterialCustom0"; + expression="_this setObjectMaterialGlobal [0,_value]"; + class Value + { + class data + { + singleType="STRING"; + value="\x\hemtt\addons\photoshoot\chroma.rvmat"; + }; + }; + }; + nAttributes=2; + }; + }; + class Item1 + { + dataType="Group"; + side="Civilian"; + class Entities + { + items=1; + class Item0 + { + dataType="Object"; + class PositionInfo + { + position[]={100,15.001439,95.050003}; + angles[]={0,3.1415927,0}; + }; + side="Civilian"; + flags=2; + class Attributes + { + name="model_clothing"; + disableSimulation=1; + ignoreByDynSimulGrid=1; + class Inventory + { + map="ItemMap"; + compass="ItemCompass"; + watch="ItemWatch"; + }; + }; + id=18; + type="C_man_1"; + atlOffset=10; + class CustomAttributes + { + class Attribute0 + { + property="allowDamage"; + expression="_this allowdamage _value;"; + class Value + { + class data + { + singleType="BOOL"; + value=0; + }; + }; + }; + class Attribute1 + { + property="face"; + expression="_this setface _value;"; + class Value + { + class data + { + singleType="STRING"; + value="HEMTTPhotoshoot"; + }; + }; + }; + class Attribute2 + { + property="speaker"; + expression="_this setspeaker _value;"; + class Value + { + class data + { + singleType="STRING"; + value="Male05GRE"; + }; + }; + }; + class Attribute3 + { + property="pitch"; + expression="_this setpitch _value;"; + class Value + { + class data + { + singleType="SCALAR"; + value=1; + }; + }; + }; + class Attribute4 + { + property="enableStamina"; + expression="_this enablestamina _value;"; + class Value + { + class data + { + singleType="BOOL"; + value=0; + }; + }; + }; + nAttributes=5; + }; + }; + }; + class Attributes + { + }; + id=1; + atlOffset=10; + }; + class Item2 + { + dataType="Object"; + class PositionInfo + { + position[]={100,16.358088,94}; + }; + side="Empty"; + class Attributes + { + name="camera_uniform"; + }; + id=6; + type="Sign_Arrow_Direction_Blue_F"; + atlOffset=11.299999; + }; + }; +}; diff --git a/bin/src/commands/dev.rs b/bin/src/commands/dev.rs index 572d0aac..275f9442 100644 --- a/bin/src/commands/dev.rs +++ b/bin/src/commands/dev.rs @@ -35,13 +35,13 @@ use super::JustArgs; /// Includes from excluded addons can be used, but they will not be built or linked. pub struct Command { #[clap(flatten)] - dev: DevArgs, + pub(crate) dev: DevArgs, #[clap(flatten)] - just: JustArgs, + pub(crate) just: JustArgs, #[clap(flatten)] - global: crate::GlobalArgs, + pub(crate) global: crate::GlobalArgs, } #[derive(clap::Args)] @@ -52,7 +52,7 @@ pub struct DevArgs { /// /// By default, `hemtt dev` will not binarize any files, but rather pack them as-is. /// Binarization is often not needed for development. - binarize: bool, + pub(crate) binarize: bool, #[arg(long, short, action = clap::ArgAction::Append, verbatim_doc_comment)] /// Include an optional addon folder /// @@ -61,24 +61,24 @@ pub struct DevArgs { /// ```bash /// hemtt dev -o caramel -o chocolate /// ``` - optional: Vec, + pub(crate) optional: Vec, #[arg(long, short = 'O', action = clap::ArgAction::SetTrue, conflicts_with = "optional", verbatim_doc_comment)] /// Include all optional addon folders - all_optionals: bool, + pub(crate) all_optionals: bool, #[arg(long, action = clap::ArgAction::SetTrue, verbatim_doc_comment)] /// Do not rapify (cpp, rvmat) /// /// They will be copied directly into the PBO, not .bin version is created. - no_rap: bool, + pub(crate) no_rap: bool, } /// Execute the dev command /// /// # Errors /// [`Error`] depending on the modules -pub fn execute(cmd: &Command, launch_optionals: &[String]) -> Result { +pub fn execute(cmd: &Command, launch_optionals: &[String]) -> Result<(Report, Context), Error> { let mut executor = context(&cmd.dev, &cmd.just, launch_optionals, false, true)?; - executor.run() + executor.run().map(|r| (r, executor.into_ctx())) } /// Create a new executor for the dev command diff --git a/bin/src/commands/launch/launcher.rs b/bin/src/commands/launch/launcher.rs new file mode 100644 index 00000000..0a633643 --- /dev/null +++ b/bin/src/commands/launch/launcher.rs @@ -0,0 +1,331 @@ +use std::{ + path::{Path, PathBuf}, + process::Child, +}; + +use hemtt_common::{arma::dlc::DLC, config::LaunchOptions, steam}; +use regex::Regex; + +use crate::{ + commands::launch::{ + error::{bcle1_preset_not_found::PresetNotFound, bcle4_arma_not_found::ArmaNotFound}, + preset, + }, + report::Report, + Error, +}; + +use super::{ + error::{ + bcle2_workshop_not_found::WorkshopNotFound, + bcle3_workshop_mod_not_found::WorkshopModNotFound, + bcle8_mission_not_found::MissionNotFound, bcle9_mission_absolute::MissionAbsolutePath, + }, + LaunchArgs, +}; + +pub struct Launcher { + executable: String, + dlc: Vec, + workshop: Vec, + options: Vec, + arma3: PathBuf, + mission: Option, + instances: u8, + file_patching: bool, +} + +impl Launcher { + pub fn new( + launch: &LaunchArgs, + options: &LaunchOptions, + ) -> Result<(Report, Option), Error> { + let mut report = Report::new(); + let Some(arma3) = steam::find_app(107_410) else { + report.push(ArmaNotFound::code()); + return Ok((report, None)); + }; + debug!("Arma 3 found at: {}", arma3.display()); + let mut launcher = Self { + instances: launch.instances.unwrap_or_else(|| options.instances()), + file_patching: options.file_patching() && !launch.no_filepatching, + executable: options.executable(), + dlc: options.dlc().to_vec(), + workshop: options.workshop().to_vec(), + mission: options.mission().map(std::string::ToString::to_string), + options: { + let mut args = ["-skipIntro", "-noSplash", "-showScriptErrors", "-debug"] + .iter() + .map(std::string::ToString::to_string) + .collect::>(); + args.append(&mut options.parameters().to_vec()); + args.append(&mut launch.passthrough.clone().unwrap_or_default()); + args + }, + arma3, + }; + for preset in options.presets() { + launcher.add_preset(preset, &mut report)?; + } + if report.failed() { + return Ok((report, None)); + } + Ok((report, Some(launcher))) + } + + pub fn add_self_mod(&mut self) -> Result<(), Error> { + self.workshop.push({ + let mut path = std::env::current_dir()?; + path.push(".hemttout/dev"); + if cfg!(target_os = "linux") { + format!("Z:{}", path.display()) + } else { + path.display().to_string() + } + }); + Ok(()) + } + + pub fn add_preset(&mut self, preset: &str, report: &mut Report) -> Result<(), Error> { + let presets = std::env::current_dir()?.join(".hemtt/presets"); + trace!("Loading preset: {}", preset); + let html = presets.join(preset).with_extension("html"); + if !html.exists() { + report.push(PresetNotFound::code(preset.to_string(), &presets)); + return Ok(()); + } + let html = std::fs::read_to_string(html)?; + let (preset_mods, preset_dlc) = preset::read(preset, &html); + for load_mod in preset_mods { + if !self.workshop.contains(&load_mod) { + self.workshop.push(load_mod); + } + } + for load_dlc in preset_dlc { + if !self.dlc.contains(&load_dlc) { + self.dlc.push(load_dlc); + } + } + Ok(()) + } + + #[allow(clippy::too_many_lines)] + pub fn launch( + &self, + mut args: Vec, + report: &mut Report, + ) -> Result, Error> { + let mut mods = Vec::new(); + if !self.workshop.is_empty() { + let Some(common) = self.arma3.parent() else { + report.push(WorkshopNotFound::code()); + return Ok(None); + }; + let Some(root) = common.parent() else { + report.push(WorkshopNotFound::code()); + return Ok(None); + }; + let workshop_folder = root.join("workshop").join("content").join("107410"); + if !workshop_folder.exists() { + report.push(WorkshopNotFound::code()); + return Ok(None); + }; + + let mut meta = None; + let meta_path = std::env::current_dir()?.join("meta.cpp"); + if meta_path.exists() { + let content = std::fs::read_to_string(meta_path)?; + let regex = Regex::new(r"publishedid\s*=\s*(\d+);").expect("meta regex compiles"); + if let Some(id) = regex.captures(&content).map(|c| c[1].to_string()) { + meta = Some(id); + } + } + + for load_mod in &self.workshop { + if Some(load_mod.clone()) == meta { + warn!( + "Skipping mod {} as it is the same as the project's meta.cpp id", + load_mod + ); + continue; + } + let mod_path = workshop_folder.join(load_mod); + if !mod_path.exists() { + report.push(WorkshopModNotFound::code(load_mod.to_string())); + }; + if cfg!(windows) { + mods.push(mod_path.display().to_string()); + } else { + mods.push(format!("Z:{}", mod_path.display())); + } + } + } + if report.failed() { + return Ok(None); + } + + let mut dlc = self.dlc.clone(); + dlc.dedup(); + for dlc in dlc { + args.push(format!("-mod=\"{}\"", dlc.to_mod())); + } + mods.dedup(); + for m in mods { + args.push(format!("-mod=\"{m}\"")); + } + args.extend(self.options.clone()); + + if let Some(mission) = &self.mission { + let mut path = PathBuf::from(mission); + + if path.is_absolute() { + report.push(MissionAbsolutePath::code(mission.to_string())); + return Ok(None); + } + path = std::env::current_dir()?.join(mission); + + if !path.ends_with("mission.sqm") { + path.push("mission.sqm"); + } + + if !path.is_file() { + path = std::env::current_dir()? + .join(".hemtt") + .join("missions") + .join(mission) + .join("mission.sqm"); + } + + if path.is_file() { + args.push(format!("\"{}\"", path.display())); + } else { + report.push(MissionNotFound::code( + mission.to_string(), + &std::env::current_dir()?, + )); + return Ok(None); + } + } + + let mut instances = Vec::new(); + for _ in 0..self.instances { + let mut args = args.clone(); + // if with_server { + if false { + args.push("-connect=127.0.0.1".to_string()); + } else if self.file_patching { + args.push("-filePatching".to_string()); + } + instances.push(args); + } + + if instances.len() == 1 { + Ok(Some(if cfg!(target_os = "windows") { + super::platforms::windows(&self.arma3, &self.executable, &instances[0])? + } else { + super::platforms::linux(&args)? + })) + } else { + let mut children = Vec::new(); + for instance in instances { + children.push(if cfg!(target_os = "windows") { + super::platforms::windows(&self.arma3, &self.executable, &instance)? + } else { + super::platforms::linux(&instance)? + }); + } + Ok(Some( + children.into_iter().next().expect("At least one child"), + )) + } + } + + #[must_use] + pub fn arma3dir(&self) -> &Path { + &self.arma3 + } + + pub fn add_dlcs(&mut self, dlcs: Vec) { + self.dlc.extend(dlcs); + } + + #[must_use] + pub fn with_dlcs(mut self, dlcs: Vec) -> Self { + self.add_dlcs(dlcs); + self + } + + #[must_use] + pub const fn dlcs(&self) -> &Vec { + &self.dlc + } + + pub fn add_mods(&mut self, mods: Vec) -> Report { + let mut report = Report::new(); + let Some(common) = self.arma3.parent() else { + report.push(WorkshopNotFound::code()); + return report; + }; + let Some(root) = common.parent() else { + report.push(WorkshopNotFound::code()); + return report; + }; + let workshop_folder = root.join("workshop").join("content").join("107410"); + if !workshop_folder.exists() { + report.push(WorkshopNotFound::code()); + return report; + }; + for m in mods { + self.workshop.push(match m { + Mod::Workshop(id) => { + let workshop = workshop_folder.join(&id); + if !workshop.exists() { + report.push(WorkshopNotFound::code()); + } + workshop.display().to_string() + } + Mod::Local(path) => path, + }); + } + report + } + + #[must_use] + pub fn with_mods(mut self, mods: Vec) -> Self { + self.add_mods(mods); + self + } + + pub fn add_external_mod(&mut self, path: String) { + self.workshop.push(if cfg!(target_os = "linux") { + format!("Z:{path}") + } else { + path + }); + } + + pub fn add_options(&mut self, options: Vec) { + self.options.extend(options); + } + + #[must_use] + pub fn with_options(mut self, options: Vec) -> Self { + self.add_options(options); + self + } +} + +pub enum Mod { + Workshop(String), + Local(String), +} + +impl Mod { + #[must_use] + pub fn path(&self, workshop: &Path) -> String { + match self { + Self::Workshop(id) => workshop.join(id).display().to_string(), + Self::Local(path) => path.clone(), + } + } +} diff --git a/bin/src/commands/launch/mod.rs b/bin/src/commands/launch/mod.rs index 8b30f8fc..bd7857fa 100644 --- a/bin/src/commands/launch/mod.rs +++ b/bin/src/commands/launch/mod.rs @@ -1,28 +1,25 @@ -mod error; +use std::path::Path; -use std::path::{Path, PathBuf}; - -use hemtt_common::{ - arma::dlc::DLC, - config::{LaunchOptions, ProjectConfig}, - steam, -}; -use regex::Regex; +use hemtt_common::config::{LaunchOptions, ProjectConfig}; +use launcher::Launcher; use crate::{ commands::launch::error::{ - bcle1_preset_not_found::PresetNotFound, bcle2_workshop_not_found::WorkshopNotFound, - bcle3_workshop_mod_not_found::WorkshopModNotFound, bcle4_arma_not_found::ArmaNotFound, bcle5_missing_main_prefix::MissingMainPrefix, bcle6_launch_config_not_found::LaunchConfigNotFound, - bcle7_can_not_quicklaunch::CanNotQuickLaunch, bcle8_mission_not_found::MissionNotFound, - bcle9_mission_absolute::MissionAbsolutePath, + bcle7_can_not_quicklaunch::CanNotQuickLaunch, }, error::Error, link::create_link, report::Report, }; +pub mod error; + +pub mod launcher; +mod platforms; +pub mod preset; + #[derive(clap::Parser)] #[command(verbatim_doc_comment)] /// Test your project @@ -188,7 +185,7 @@ pub struct Command { global: crate::GlobalArgs, } -#[derive(clap::Args)] +#[derive(Default, clap::Args)] #[allow(clippy::module_name_repetitions)] pub struct LaunchArgs { #[arg(action = clap::ArgAction::Append, verbatim_doc_comment)] @@ -244,193 +241,31 @@ pub struct LaunchArgs { /// # Panics /// Will panic if the regex can not be compiled, which should never be the case in a released version pub fn execute(cmd: &Command) -> Result { - let config = ProjectConfig::from_file(&Path::new(".hemtt").join("project.toml"))?; let mut report = Report::new(); + let config = ProjectConfig::from_file(&Path::new(".hemtt").join("project.toml"))?; let Some(mainprefix) = config.mainprefix() else { report.push(MissingMainPrefix::code()); return Ok(report); }; - let configs = cmd.launch.config.clone().unwrap_or_default(); - - let launch = if configs.is_empty() { - config - .hemtt() - .launch() - .get("default") - .cloned() - .unwrap_or_default() - } else if let Some(launch) = configs - .into_iter() - .map(|c| { - config.hemtt().launch().get(&c).cloned().map_or_else( - || { - report.push(LaunchConfigNotFound::code( - c.to_string(), - &config.hemtt().launch().keys().cloned().collect::>(), - )); - None - }, - Some, - ) - }) - .collect::>>() - { - launch.into_iter().fold( - LaunchOptions::default(), - hemtt_common::config::LaunchOptions::overlay, - ) - } else { + let launch = read_config( + &config, + cmd.launch.config.as_deref().unwrap_or_default(), + &mut report, + )?; + let Some(launch) = launch else { return Ok(report); }; trace!("launch config: {:?}", launch); - let instance_count = cmd.launch.instances.unwrap_or_else(|| launch.instances()); + let (mut report, launcher) = Launcher::new(&cmd.launch, &launch)?; - let Some(arma3dir) = steam::find_app(107_410) else { - report.push(ArmaNotFound::code()); + let Some(mut launcher) = launcher else { return Ok(report); }; - debug!("Arma 3 found at: {}", arma3dir.display()); - - let mut mods = Vec::new(); - - mods.push({ - let mut path = std::env::current_dir()?; - path.push(".hemttout/dev"); - if cfg!(target_os = "linux") { - format!("Z:{}", path.display()) - } else { - path.display().to_string() - } - }); - - let mut meta = None; - let meta_path = std::env::current_dir()?.join("meta.cpp"); - if meta_path.exists() { - let content = std::fs::read_to_string(meta_path)?; - let regex = Regex::new(r"publishedid\s*=\s*(\d+);").expect("meta regex compiles"); - if let Some(id) = regex.captures(&content).map(|c| c[1].to_string()) { - meta = Some(id); - } - } - - let mut workshop = launch.workshop().to_vec(); - let mut dlc = launch.dlc().to_vec(); - - let presets = std::env::current_dir()?.join(".hemtt/presets"); - for preset in launch.presets() { - trace!("Loading preset: {}", preset); - let html = presets.join(preset).with_extension("html"); - if !html.exists() { - report.push(PresetNotFound::code(preset.to_string(), &presets)); - continue; - } - let html = std::fs::read_to_string(html)?; - let (preset_mods, preset_dlc) = read_preset(preset, &html); - for load_mod in preset_mods { - if !workshop.contains(&load_mod) { - workshop.push(load_mod); - } - } - for load_dlc in preset_dlc { - if !dlc.contains(&load_dlc) { - dlc.push(load_dlc); - } - } - } - if report.failed() { - return Ok(report); - } - - // climb to the workshop folder - if !workshop.is_empty() { - let Some(common) = arma3dir.parent() else { - report.push(WorkshopNotFound::code()); - return Ok(report); - }; - let Some(root) = common.parent() else { - report.push(WorkshopNotFound::code()); - return Ok(report); - }; - let workshop_folder = root.join("workshop").join("content").join("107410"); - if !workshop_folder.exists() { - report.push(WorkshopNotFound::code()); - return Ok(report); - }; - for load_mod in workshop { - if Some(load_mod.clone()) == meta { - warn!( - "Skipping mod {} as it is the same as the project's meta.cpp id", - load_mod - ); - continue; - } - let mod_path = workshop_folder.join(&load_mod); - if !mod_path.exists() { - report.push(WorkshopModNotFound::code(load_mod)); - }; - if cfg!(windows) { - mods.push(mod_path.display().to_string()); - } else { - mods.push(format!("Z:{}", mod_path.display())); - } - } - } - if report.failed() { - return Ok(report); - } - - for dlc in dlc { - mods.push(dlc.to_mod().to_string()); - } - - let mut args: Vec = ["-skipIntro", "-noSplash", "-showScriptErrors", "-debug"] - .iter() - .map(std::string::ToString::to_string) - .collect(); - args.append(&mut launch.parameters().to_vec()); - args.append(&mut cmd.launch.passthrough.clone().unwrap_or_default()); - args.push( - mods.iter() - .map(|s| format!("-mod=\"{s}\"")) - .collect::>() - .join(" "), - ); - - if let Some(mission) = launch.mission() { - let mut path = PathBuf::from(mission); - - if path.is_absolute() { - report.push(MissionAbsolutePath::code(mission.to_string())); - return Ok(report); - } - path = std::env::current_dir()?.join(mission); - - if !path.ends_with("mission.sqm") { - path.push("mission.sqm"); - } - - if !path.is_file() { - path = std::env::current_dir()? - .join(".hemtt") - .join("missions") - .join(mission) - .join("mission.sqm"); - } - - if path.is_file() { - args.push(format!("\"{}\"", path.display())); - } else { - report.push(MissionNotFound::code( - mission.to_string(), - &std::env::current_dir()?, - )); - return Ok(report); - } - } + launcher.add_self_mod()?; if cmd.launch.no_build { warn!("Using Quick Launch! HEMTT will not rebuild the project"); @@ -441,7 +276,7 @@ pub fn execute(cmd: &Command) -> Result { return Ok(report); } - let prefix_folder = arma3dir.join(mainprefix); + let prefix_folder = launcher.arma3dir().join(mainprefix); let link = prefix_folder.join(config.prefix()); if !prefix_folder.exists() || !link.exists() { report.push(CanNotQuickLaunch::code( @@ -464,7 +299,7 @@ pub fn execute(cmd: &Command) -> Result { return Ok(report); } - let prefix_folder = arma3dir.join(mainprefix); + let prefix_folder = launcher.arma3dir().join(mainprefix); if !prefix_folder.exists() { std::fs::create_dir_all(&prefix_folder)?; } @@ -478,145 +313,45 @@ pub fn execute(cmd: &Command) -> Result { } } - // let with_server = matches.get_flag("server"); - let with_server = false; - - let mut instances = vec![]; - if with_server { - let mut args = args.clone(); - args.push("-server".to_string()); - instances.push(args); - } - for _ in 0..instance_count { - let mut args = args.clone(); - if with_server { - args.push("-connect=127.0.0.1".to_string()); - } else if launch.file_patching() && !cmd.launch.no_filepatching { - args.push("-filePatching".to_string()); - } - instances.push(args); - } - - if cfg!(target_os = "windows") { - let mut path = arma3dir.clone(); - if let Some(exe) = &cmd.launch.executable { - let exe = PathBuf::from(exe); - if exe.is_absolute() { - path = exe; - } else { - path.push(exe); - } - path.set_extension("exe"); - } else { - path.push(launch.executable()); - } - for instance in instances { - windows_launch(&arma3dir, &path, &instance)?; - } - } else { - if launch.executable() != "arma3_x64.exe" { - warn!("Currently, only Windows supports specifying the executable"); - } - for instance in instances { - linux_launch(&instance)?; - } - } + launcher.launch(Vec::new(), &mut report)?; Ok(report) } -/// Read a preset file and return the mods and DLCs -/// -/// # Panics -/// Will panic if the regex can not be compiled, which should never be the case in a released version -pub fn read_preset(name: &str, html: &str) -> (Vec, Vec) { - let mut workshop = Vec::new(); - let mut dlc = Vec::new(); - let mod_regex = Regex::new( - r#"(?m)href="https?:\/\/steamcommunity\.com\/sharedfiles\/filedetails\/\?id=(\d+)""#, - ) - .expect("mod regex compiles"); - for id in mod_regex.captures_iter(html).map(|c| c[1].to_string()) { - if workshop.contains(&id) { - trace!("Skipping mod {} in preset {}", id, name); - } else { - trace!("Found new mod {} in preset {}", id, name); - workshop.push(id); - } - } - let dlc_regex = Regex::new(r#"(?m)href="https?:\/\/store\.steampowered\.com\/app\/(\d+)""#) - .expect("dlc regex compiles"); - for id in dlc_regex.captures_iter(html).map(|c| c[1].to_string()) { - let Ok(preset_dlc) = DLC::try_from(id.clone()) else { - warn!( - "Preset {} requires DLC {}, but HEMTT does not recognize it", - name, id - ); - continue; - }; - if dlc.contains(&preset_dlc) { - trace!("Skipping DLC {} in preset {}", id, name); - } else { - trace!("Found new DLC {} in preset {}", id, name); - dlc.push(preset_dlc); - } - } - (workshop, dlc) -} - -fn windows_launch(arma3dir: &Path, executable: &PathBuf, args: &[String]) -> Result<(), Error> { - info!( - "Launching {:?} with:\n {}", - arma3dir.display(), - args.join("\n ") - ); - std::process::Command::new(executable).args(args).spawn()?; - Ok(()) -} - -fn linux_launch(args: &[String]) -> Result<(), Error> { - // check if flatpak steam is installed - let flatpak = std::process::Command::new("flatpak") - .arg("list") - .arg("--app") - .output() - .map(|o| String::from_utf8_lossy(&o.stdout).contains("com.valvesoftware.Steam"))?; - if flatpak { - warn!( - "A flatpak override will be created to grant Steam access to the .hemttout directory" - ); - info!("Using flatpak steam with:\n {}", args.join("\n ")); - std::process::Command::new("flatpak") - .arg("override") - .arg("--user") - .arg("com.valvesoftware.Steam") - .arg(format!("--filesystem={}", { - let mut path = std::env::current_dir()?; - path.push(".hemttout/dev"); - path.display().to_string() - })) - .spawn()? - .wait()?; - std::process::Command::new("flatpak") - .arg("run") - .arg("com.valvesoftware.Steam") - .arg("-applaunch") - .arg("107410") - .arg("-nolauncher") - .args(args) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .spawn()?; +pub fn read_config( + config: &ProjectConfig, + configs: &[String], + report: &mut Report, +) -> Result, Error> { + let launch = if configs.is_empty() { + config + .hemtt() + .launch() + .get("default") + .cloned() + .unwrap_or_default() + } else if let Some(launch) = configs + .iter() + .map(|c| { + config.hemtt().launch().get(c).cloned().map_or_else( + || { + report.push(LaunchConfigNotFound::code( + c.to_string(), + &config.hemtt().launch().keys().cloned().collect::>(), + )); + None + }, + Some, + ) + }) + .collect::>>() + { + launch.into_iter().fold( + LaunchOptions::default(), + hemtt_common::config::LaunchOptions::overlay, + ) } else { - info!("Using native steam with:\n {}", args.join("\n ")); - std::process::Command::new("steam") - .arg("-applaunch") - .arg("107410") - .arg("-nolauncher") - .args(args) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .spawn()?; - } - Ok(()) + return Ok(None); + }; + Ok(Some(launch)) } diff --git a/bin/src/commands/launch/platforms.rs b/bin/src/commands/launch/platforms.rs new file mode 100644 index 00000000..4bfc7658 --- /dev/null +++ b/bin/src/commands/launch/platforms.rs @@ -0,0 +1,70 @@ +use std::{ + path::{Path, PathBuf}, + process::Child, +}; + +use crate::Error; + +pub fn windows(arma3: &Path, executable: &str, args: &[String]) -> Result { + let mut path = arma3.to_path_buf(); + let exe = PathBuf::from(executable); + if exe.is_absolute() { + path = exe; + } else { + path.push(exe); + } + path.set_extension("exe"); + info!( + "Launching {:?} with:\n {}", + arma3.display(), + args.join("\n ") + ); + Ok(std::process::Command::new(path).args(args).spawn()?) +} + +pub fn linux(args: &[String]) -> Result { + // check if flatpak steam is installed + let flatpak = std::process::Command::new("flatpak") + .arg("list") + .arg("--app") + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).contains("com.valvesoftware.Steam"))?; + let child = if flatpak { + warn!( + "A flatpak override will be created to grant Steam access to the .hemttout directory" + ); + info!("Using flatpak steam with:\n {}", args.join("\n ")); + std::process::Command::new("flatpak") + .arg("override") + .arg("--user") + .arg("com.valvesoftware.Steam") + .arg(format!("--filesystem={}", { + let mut path = std::env::current_dir()?; + path.push(".hemttout/dev"); + path.display().to_string() + })) + .spawn()? + .wait()?; + std::process::Command::new("flatpak") + .arg("run") + .arg("com.valvesoftware.Steam") + .arg("-applaunch") + .arg("107410") + .arg("-nolauncher") + .args(args) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn()? + } else { + info!("Using native steam with:\n {}", args.join("\n ")); + std::process::Command::new("steam") + .arg("-applaunch") + .arg("107410") + .arg("-nolauncher") + .args(args) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn()? + }; + Ok(child) +} diff --git a/bin/src/commands/launch/preset.rs b/bin/src/commands/launch/preset.rs new file mode 100644 index 00000000..564e68d6 --- /dev/null +++ b/bin/src/commands/launch/preset.rs @@ -0,0 +1,41 @@ +use hemtt_common::arma::dlc::DLC; +use regex::Regex; + +/// Read a preset file and return the mods and DLCs +/// +/// # Panics +/// Will panic if the regex can not be compiled, which should never be the case in a released version +pub fn read(name: &str, html: &str) -> (Vec, Vec) { + let mut workshop = Vec::new(); + let mut dlc = Vec::new(); + let mod_regex = Regex::new( + r#"(?m)href="https?:\/\/steamcommunity\.com\/sharedfiles\/filedetails\/\?id=(\d+)""#, + ) + .expect("mod regex compiles"); + for id in mod_regex.captures_iter(html).map(|c| c[1].to_string()) { + if workshop.contains(&id) { + trace!("Skipping mod {} in preset {}", id, name); + } else { + trace!("Found new mod {} in preset {}", id, name); + workshop.push(id); + } + } + let dlc_regex = Regex::new(r#"(?m)href="https?:\/\/store\.steampowered\.com\/app\/(\d+)""#) + .expect("dlc regex compiles"); + for id in dlc_regex.captures_iter(html).map(|c| c[1].to_string()) { + let Ok(preset_dlc) = DLC::try_from(id.clone()) else { + warn!( + "Preset {} requires DLC {}, but HEMTT does not recognize it", + name, id + ); + continue; + }; + if dlc.contains(&preset_dlc) { + trace!("Skipping DLC {} in preset {}", id, name); + } else { + trace!("Found new DLC {} in preset {}", id, name); + dlc.push(preset_dlc); + } + } + (workshop, dlc) +} diff --git a/bin/src/commands/mod.rs b/bin/src/commands/mod.rs index d6e2994b..a804dae3 100644 --- a/bin/src/commands/mod.rs +++ b/bin/src/commands/mod.rs @@ -5,6 +5,7 @@ pub mod dev; pub mod launch; pub mod localization; pub mod new; +pub mod photoshoot; pub mod release; pub mod script; pub mod utils; diff --git a/bin/src/commands/photoshoot/error/bcpe1_tools_not_found.rs b/bin/src/commands/photoshoot/error/bcpe1_tools_not_found.rs new file mode 100644 index 00000000..454ff8fe --- /dev/null +++ b/bin/src/commands/photoshoot/error/bcpe1_tools_not_found.rs @@ -0,0 +1,32 @@ +use std::sync::Arc; + +use hemtt_workspace::reporting::{Code, Diagnostic}; + +pub struct ToolsNotFound; + +impl Code for ToolsNotFound { + fn ident(&self) -> &'static str { + "BCPE1" + } + + fn message(&self) -> String { + String::from("Arma 3 Tools not found in registry.") + } + + fn help(&self) -> Option { + Some(String::from( + "Install Arma 3 Tools from Steam and run them at least once.", + )) + } + + fn diagnostic(&self) -> Option { + Some(Diagnostic::from_code(self)) + } +} + +impl ToolsNotFound { + #[allow(dead_code)] // used in windows only + pub fn code() -> Arc { + Arc::new(Self) + } +} diff --git a/bin/src/commands/photoshoot/error/mod.rs b/bin/src/commands/photoshoot/error/mod.rs new file mode 100644 index 00000000..ddee5b5a --- /dev/null +++ b/bin/src/commands/photoshoot/error/mod.rs @@ -0,0 +1 @@ +pub mod bcpe1_tools_not_found; diff --git a/bin/src/commands/photoshoot/mod.rs b/bin/src/commands/photoshoot/mod.rs new file mode 100644 index 00000000..ec5a57dd --- /dev/null +++ b/bin/src/commands/photoshoot/mod.rs @@ -0,0 +1,399 @@ +#![allow(clippy::unwrap_used)] // Expiremental feature + +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::Mutex, +}; + +use hemtt_common::{ + arma::control::{ + fromarma::{self, Message}, + toarma, + }, + config::ProjectConfig, +}; +use hemtt_config::{Class, Config, Property, Value}; + +use crate::{ + context::{Context, PreservePrevious}, + controller::{Action, Controller}, + error::Error, + modules::AddonConfigs, + report::Report, + utils, +}; + +mod error; + +use self::error::bcpe1_tools_not_found::ToolsNotFound; + +use super::{ + dev, + launch::{read_config, LaunchArgs}, + JustArgs, +}; + +#[derive(clap::Parser)] +pub struct Command { + #[clap(flatten)] + global: crate::GlobalArgs, +} + +#[allow(clippy::too_many_lines)] +pub fn execute(cmd: &Command) -> Result { + // Warn the user this is experimental, ask them to confirm + if !dialoguer::Confirm::new() + .with_prompt("This feature is experimental, are you sure you want to continue?") + .interact() + .unwrap() { + return Ok(Report::new()); + } + + let mut report = Report::new(); + let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + let Ok(key) = hkcu.open_subkey("Software\\Bohemia Interactive\\ImageToPAA") else { + report.push(ToolsNotFound::code()); + return Ok(report); + }; + let Ok(path) = key.get_value::("tool") else { + report.push(ToolsNotFound::code()); + return Ok(report); + }; + let command = PathBuf::from(path); + if !command.exists() { + report.push(ToolsNotFound::code()); + return Ok(report); + } + + let mut report = Report::new(); + if cfg!(windows) && !cfg!(target_pointer_width = "64") { + error!("Photoshoot is only supported on 64 bit Windows"); + return Ok(report); + } + + let config = ProjectConfig::from_file(&Path::new(".hemtt").join("project.toml"))?; + let launch = if config.hemtt().launch().contains_key("photoshoot") { + read_config(&config, &[String::from("photoshoot")], &mut report)? + } else { + read_config(&config, &[], &mut report)? + }; + let Some(launch) = launch else { + return Ok(report); + }; + + let (report, dev_ctx) = super::dev::execute( + &dev::Command { + global: cmd.global.clone(), + dev: dev::DevArgs { + optional: Vec::new(), + all_optionals: true, + binarize: false, + no_rap: false, + }, + just: JustArgs { just: Vec::new() }, + }, + launch.optionals(), + )?; + if report.failed() { + return Ok(report); + } + let ctx = Context::new(Some("photoshoot"), PreservePrevious::Remove, false)?; + + let mut ps = Photoshoot::new(command, ctx.profile().join("Users/hemtt/Screenshots")); + + ps.add_weapons(find_weapons(&dev_ctx)); + ps.add_previews(find_previews(&dev_ctx)); + + if !ps.prepare() { + return Ok(report); + } + + let mut controller = Controller::new(); + controller.add_action(Box::new(ps)); + controller.run(&ctx, &LaunchArgs::default(), &launch)?; + + Ok(report) +} + +pub struct Photoshoot { + weapons: HashMap, + previews: HashMap, + pending: Mutex>, + from: PathBuf, + command: PathBuf, +} + +impl Photoshoot { + pub fn new(command: PathBuf, from: PathBuf) -> Self { + Self { + command, + from, + weapons: HashMap::new(), + previews: HashMap::new(), + pending: Mutex::new(Vec::new()), + } + } + + fn add_weapons(&mut self, weapons: HashMap) { + self.weapons.extend(weapons); + } + + fn add_previews(&mut self, previews: HashMap) { + self.previews.extend(previews); + } + + fn prepare(&self) -> bool { + let mut pending = self.pending.lock().unwrap(); + pending.extend( + self.weapons + .keys() + .map(|weapon| toarma::Photoshoot::Weapon(weapon.clone())), + ); + if pending.is_empty() && self.previews.is_empty() { + info!("No missing items to photoshoot"); + return false; + } + drop(pending); + true + } + + fn next_message(&self) -> toarma::Message { + let mut pending = self.pending.lock().unwrap(); + pending.pop().map_or_else( + || toarma::Message::Photoshoot(toarma::Photoshoot::Done), + toarma::Message::Photoshoot, + ) + } +} + +impl Action for Photoshoot { + fn missions(&self, _: &Context) -> Vec<(String, String)> { + vec![(String::from("photoshoot"), String::from("photoshoot.VR"))] + } + + fn incoming(&self, ctx: &Context, msg: fromarma::Message) -> Vec { + let Message::Photoshoot(msg) = msg else { + return Vec::new(); + }; + match msg { + fromarma::Photoshoot::Ready => { + debug!("Photoshoot: Ready"); + if self.previews.is_empty() { + vec![self.next_message()] + } else { + let mut messages = Vec::new(); + for class in self.previews.keys() { + messages.push(toarma::Message::Photoshoot(toarma::Photoshoot::Preview( + class.clone(), + ))); + } + messages.push(toarma::Message::Photoshoot(toarma::Photoshoot::PreviewRun)); + messages + } + } + fromarma::Photoshoot::Weapon(weapon) => { + debug!("Photoshoot: Weapon: {}", weapon); + let target = + PathBuf::from(self.weapons.get(&weapon).expect("received unknown weapon")); + if target.exists() { + warn!("Target already exists: {}", target.display()); + return vec![self.next_message()]; + } + let image = utils::photoshoot::Photoshoot::weapon(&weapon, &self.from).unwrap(); + let dst_png = ctx + .build_folder() + .expect("photoshoot has a folder") + .join(format!("{weapon}_ca.png")); + image.save(&dst_png).unwrap(); + std::process::Command::new(&self.command) + .arg(dst_png) + .output() + .expect("failed to execute process"); + let dst_paa = ctx + .build_folder() + .expect("photoshoot has a folder") + .join(format!("{weapon}_ca.paa")); + std::fs::create_dir_all(target.parent().unwrap()).unwrap(); + info!("Created `{}` at `{}`", weapon, target.display()); + std::fs::rename(dst_paa, target).unwrap(); + vec![self.next_message()] + } + fromarma::Photoshoot::Previews => { + debug!("Photoshoot: Previews"); + let source = self + .from + .join("EditorPreviews") + .join(".hemttout") + .join("dev"); + for image in source.read_dir().unwrap() { + let src = image.unwrap().path(); + let target = PathBuf::from( + self.previews + .get(&src.file_stem().unwrap().to_string_lossy().to_string()) + .expect("received unknown preview"), + ); + let image = utils::photoshoot::Photoshoot::preview(&src).unwrap(); + std::fs::create_dir_all(target.parent().unwrap()).unwrap(); + info!( + "Created `{}` at `{}`", + src.file_stem().unwrap().to_string_lossy().to_string(), + target.display() + ); + image.save(target).unwrap(); + } + vec![self.next_message()] + } + } + } +} + +fn find_weapons(ctx: &Context) -> HashMap { + let mut weapons = HashMap::new(); + ctx.state() + .get::() + .read() + .unwrap() + .iter() + .for_each(|(_, config)| { + weapons.extend(weapons_from_config(ctx, config)); + }); + weapons +} + +fn weapons_from_config(ctx: &Context, config: &Config) -> HashMap { + let Some(mainprefix) = ctx.config().mainprefix() else { + return HashMap::new(); + }; + let mainprefix = format!("\\{mainprefix}\\"); + let mut weapons = HashMap::new(); + config.0.iter().for_each(|root| { + if let Property::Class(Class::Local { + name, properties, .. + }) = root + { + if name.as_str() != "CfgWeapons" { + return; + } + for prop in properties { + if let Property::Class(Class::Local { + name, properties, .. + }) = prop + { + trace!("Weapon: {}", name.as_str()); + let Some(picture) = properties.iter().find_map(|prop| { + if let Property::Entry { + name, + value: Value::Str(value), + .. + } = prop + { + if name.as_str() == "picture" { + Some(value.value().to_string()) + } else { + None + } + } else { + None + } + }) else { + continue; + }; + if picture.starts_with(&mainprefix) { + let picture = picture.trim_start_matches(&mainprefix); + if picture.starts_with(ctx.config().prefix()) { + let picture = picture + .trim_start_matches(ctx.config().prefix()) + .trim_start_matches('\\'); + let image = ctx + .workspace_path() + .join(picture.replace('\\', "/")) + .unwrap(); + if image.exists().unwrap_or_default() { + continue; + } + debug!("Image not found: {}", image.as_str()); + weapons.insert(name.as_str().to_string(), picture.to_owned()); + } + } + } + } + } + }); + weapons +} + +fn find_previews(ctx: &Context) -> HashMap { + let mut previews = HashMap::new(); + ctx.state() + .get::() + .read() + .unwrap() + .iter() + .for_each(|(_, config)| { + previews.extend(previews_from_config(ctx, config)); + }); + previews +} + +fn previews_from_config(ctx: &Context, config: &Config) -> HashMap { + let Some(mainprefix) = ctx.config().mainprefix() else { + return HashMap::new(); + }; + let mainprefix = format!("\\{mainprefix}\\"); + let mut weapons = HashMap::new(); + config.0.iter().for_each(|root| { + if let Property::Class(Class::Local { + name, properties, .. + }) = root + { + if name.as_str() != "CfgVehicles" { + return; + } + for prop in properties { + if let Property::Class(Class::Local { + name, properties, .. + }) = prop + { + trace!("Preview: {}", name.as_str()); + let Some(picture) = properties.iter().find_map(|prop| { + if let Property::Entry { + name, + value: Value::Str(value), + .. + } = prop + { + if name.as_str() == "editorPreview" { + Some(value.value().to_string()) + } else { + None + } + } else { + None + } + }) else { + continue; + }; + if picture.starts_with(&mainprefix) { + let picture = picture.trim_start_matches(&mainprefix); + if picture.starts_with(ctx.config().prefix()) { + let picture = picture + .trim_start_matches(ctx.config().prefix()) + .trim_start_matches('\\'); + let image = ctx + .workspace_path() + .join(picture.replace('\\', "/")) + .unwrap(); + if image.exists().unwrap_or_default() { + continue; + } + debug!("Image not found: {}", image.as_str()); + weapons.insert(name.as_str().to_string(), picture.to_owned()); + } + } + } + } + } + }); + weapons +} diff --git a/bin/src/context.rs b/bin/src/context.rs index 9a924f9b..4e0bd3b4 100644 --- a/bin/src/context.rs +++ b/bin/src/context.rs @@ -2,6 +2,7 @@ use std::{ env::temp_dir, fs::{create_dir_all, remove_dir_all}, path::PathBuf, + sync::Arc, }; use hemtt_common::config::ProjectConfig; @@ -18,6 +19,8 @@ pub enum PreservePrevious { Keep, } +pub type State = state::TypeMap![Send + Sync]; + #[derive(Debug, Clone)] pub struct Context { config: ProjectConfig, @@ -30,6 +33,8 @@ pub struct Context { out_folder: PathBuf, build_folder: Option, tmp: PathBuf, + profile: PathBuf, + state: Arc, } impl Context { @@ -63,8 +68,9 @@ impl Context { tmp = tmp.join(whoami::username()); } tmp - } - .join( + }; + let profile = tmp.join("profile"); + let tmp = tmp.join( root.components() .skip(2) .collect::() @@ -123,6 +129,8 @@ impl Context { out_folder, build_folder: maybe_build_folder, tmp, + profile, + state: Arc::new(State::default()), }) } @@ -206,6 +214,18 @@ impl Context { pub const fn tmp(&self) -> &PathBuf { &self.tmp } + + #[must_use] + /// %temp%/hemtt/profile + pub const fn profile(&self) -> &PathBuf { + &self.profile + } + + #[must_use] + /// The state of the context + pub fn state(&self) -> Arc { + self.state.clone() + } } fn version_check( diff --git a/bin/src/controller/action.rs b/bin/src/controller/action.rs new file mode 100644 index 00000000..9bd45664 --- /dev/null +++ b/bin/src/controller/action.rs @@ -0,0 +1,11 @@ +use hemtt_common::arma::control::{fromarma, toarma}; + +use crate::context::Context; + +pub trait Action { + fn missions(&self, ctx: &Context) -> Vec<(String, String)>; + fn incoming(&self, ctx: &Context, msg: fromarma::Message) -> Vec; + fn outgoing(&self, _ctx: &Context) -> Vec { + Vec::new() + } +} diff --git a/bin/src/controller/mod.rs b/bin/src/controller/mod.rs new file mode 100644 index 00000000..10a6e602 --- /dev/null +++ b/bin/src/controller/mod.rs @@ -0,0 +1,176 @@ +#![allow(clippy::unwrap_used)] // Experimental feature + +use std::{ + io::{Read, Write}, + process::Child, +}; + +use hemtt_common::{ + arma::control::{fromarma, toarma}, + config::LaunchOptions, +}; +use interprocess::local_socket::{ + traits::Listener, GenericNamespaced, ListenerNonblockingMode, ListenerOptions, ToNsName, +}; + +use crate::{ + commands::launch::{launcher::Launcher, LaunchArgs}, + context::Context, + error::Error, + report::Report, +}; + +mod action; +mod profile; + +pub use action::Action; + +#[derive(Default)] +pub struct Controller { + pub actions: Vec>, +} + +impl Controller { + #[must_use] + pub fn new() -> Self { + Self { actions: vec![] } + } + + pub fn add_action(&mut self, action: Box) { + self.actions.push(action); + } + + pub fn run( + self, + ctx: &Context, + launch_args: &LaunchArgs, + launch_options: &LaunchOptions, + ) -> Result { + let mut missions = vec![]; + for action in &self.actions { + action + .missions(ctx) + .iter() + .for_each(|m| missions.push(m.clone())); + } + profile::setup(ctx)?; + profile::autotest(ctx, &missions)?; + let (report, child) = launch(ctx, launch_args, launch_options)?; + let Some(mut child) = child else { + return Ok(report); + }; + let opts = ListenerOptions::new().name("hemtt_arma".to_ns_name::()?); + let socket = opts.create_sync()?; + socket.set_nonblocking(ListenerNonblockingMode::Both)?; + info!("Waiting for Arma..."); + let start = std::time::Instant::now(); + let mut did_warn = false; + let mut socket = loop { + if let Ok(s) = socket.accept() { + break s; + } + if !did_warn && start.elapsed().as_secs() > 120 { + warn!("Still waiting after 120 seconds"); + did_warn = true; + } + }; + + info!("Connected!"); + + let mut current = None; + + loop { + let status = child.try_wait(); + if status.is_err() { + warn!("No longer able to determine Arma's status"); + break; + } + if let Ok(Some(_)) = status { + info!("Arma has exited"); + break; + } + + let mut len_buf = [0u8; 4]; + if socket.read_exact(&mut len_buf).is_ok() && !len_buf.is_empty() { + let len = u32::from_le_bytes(len_buf); + trace!("Receiving: {}", len); + let mut buf = vec![0u8; len as usize]; + socket.read_exact(&mut buf).unwrap(); + let buf = String::from_utf8(buf).unwrap(); + let message: fromarma::Message = serde_json::from_str(&buf)?; + trace!("Received: {:?}", message); + if let fromarma::Message::Control(control) = message { + match control { + fromarma::Control::Mission(mission) => { + if let Some((_, mission)) = mission.split_once("\\autotest\\") { + debug!("Mission: {}", mission); + current = Some(mission.replace('\\', "")); + } else { + warn!("Invalid mission: {}", mission); + } + } + } + } else if let Some(current) = ¤t { + trace!("msg for {current}: {message:?}"); + self.actions + .iter() + .find(|a| a.missions(ctx).iter().any(|m| &m.1 == current)) + .unwrap() + .incoming(ctx, message) + .iter() + .for_each(|m| send(m, &mut socket)); + } else { + warn!("Message without mission: {:?}", message); + } + } + } + Ok(report) + } +} + +fn launch( + ctx: &Context, + launch_args: &LaunchArgs, + launch_options: &LaunchOptions, +) -> Result<(Report, Option), Error> { + let (mut report, launcher) = Launcher::new(launch_args, launch_options)?; + + let Some(mut launcher) = launcher else { + return Ok((report, None)); + }; + launcher.add_self_mod()?; + + let mut args: Vec = ["-name=hemtt", "-window"] + .iter() + .map(std::string::ToString::to_string) + .collect(); + args.push(format!( + "-autotest={}", + ctx.profile() + .join("Users/hemtt/autotest.cfg") + .display() + .to_string() + .replace('/', "\\") + )); + args.insert(0, format!("-profiles={}", ctx.profile().display())); + args.push(format!("-cfg=\"{}\\arma3.cfg\"", ctx.profile().display())); + args.push(format!("-mod=\"{}\\@hemtt\"", ctx.profile().display())); + + let child = launcher.launch(args, &mut report)?; + Ok((report, child)) +} + +#[allow(clippy::cast_possible_truncation)] +fn send( + message: &toarma::Message, + socket: &mut interprocess::local_socket::prelude::LocalSocketStream, +) { + let message = serde_json::to_string(message).unwrap(); + trace!("sending: {}", message); + socket + .write_all(&u32::to_le_bytes(message.len() as u32)) + .unwrap(); + socket.write_all(message.as_bytes()).unwrap(); + socket.flush().unwrap(); + std::thread::sleep(std::time::Duration::from_millis(100)); +} diff --git a/bin/src/controller/profile.rs b/bin/src/controller/profile.rs new file mode 100644 index 00000000..46a74ba8 --- /dev/null +++ b/bin/src/controller/profile.rs @@ -0,0 +1,50 @@ +#![allow(clippy::unwrap_used)] // Experimental feature + +use std::{fs::File, io::Write}; + +use rust_embed::RustEmbed; + +use crate::{context::Context, error::Error}; + +#[cfg(windows)] +#[derive(RustEmbed)] +#[folder = "dist/profile"] +struct Distributables; + +pub fn setup(ctx: &Context) -> Result<(), Error> { + if ctx.profile().exists() { + std::fs::remove_dir_all(ctx.profile())?; + } + std::fs::create_dir_all(ctx.profile())?; + for file in Distributables::iter() { + let file = file.to_string(); + trace!("unpacking {:?}", file); + let path = ctx.profile().join(&file); + std::fs::create_dir_all(path.parent().unwrap())?; + let mut f = File::create(&path)?; + f.write_all(&Distributables::get(&file).unwrap().data)?; + } + Ok(()) +} + +pub fn autotest(ctx: &Context, missions: &[(String, String)]) -> Result<(), Error> { + let mut autotest = File::create(ctx.profile().join("Users/hemtt/autotest.cfg"))?; + autotest.write_all(b"class TestMissions {")?; + for (name, file) in missions { + autotest.write_all( + format!( + r#"class {} {{campaign = "";mission = "{}\autotest\{}";}};"#, + name, + ctx.profile() + .display() + .to_string() + .replace('/', "\\") + .replace('\n', "\r\n"), + file + ) + .as_bytes(), + )?; + } + autotest.write_all(b"};")?; + Ok(()) +} diff --git a/bin/src/error.rs b/bin/src/error.rs index 4fa8cd67..dfa5ca16 100644 --- a/bin/src/error.rs +++ b/bin/src/error.rs @@ -37,6 +37,8 @@ pub enum Error { GlobError(#[from] glob::GlobError), #[error("Glob Pattern Error: {0}")] GlobPattern(#[from] glob::PatternError), + #[error("Image Error: {0}")] + Image(#[from] image::ImageError), #[error("IO Error: {0}")] Io(#[from] std::io::Error), #[error("serde_json Error: {0}")] diff --git a/bin/src/executor.rs b/bin/src/executor.rs index 48b62351..4108a180 100644 --- a/bin/src/executor.rs +++ b/bin/src/executor.rs @@ -29,6 +29,11 @@ impl Executor { &self.ctx } + #[must_use] + pub fn into_ctx(self) -> Context { + self.ctx + } + pub fn collapse(&mut self, collpase: Collapse) { self.collapse = collpase; } diff --git a/bin/src/lib.rs b/bin/src/lib.rs index 10968b34..59f5c2e0 100644 --- a/bin/src/lib.rs +++ b/bin/src/lib.rs @@ -6,6 +6,7 @@ extern crate tracing; pub mod commands; pub mod context; +pub mod controller; pub mod error; pub mod executor; pub mod link; @@ -28,7 +29,7 @@ pub struct Cli { global: GlobalArgs, } -#[derive(clap::Args)] +#[derive(Clone, clap::Args)] pub struct GlobalArgs { #[arg(global = true, long, short)] /// Number of threads, defaults to # of CPUs @@ -58,6 +59,7 @@ enum Commands { #[clap(alias = "ln")] Localization(commands::localization::Command), Script(commands::script::Command), + Photoshoot(commands::photoshoot::Command), Utils(commands::utils::Command), Value(commands::value::Command), Wiki(commands::wiki::Command), @@ -142,12 +144,13 @@ pub fn execute(cli: &Cli) -> Result<(), Error> { Commands::Book(ref cmd) => commands::book::execute(cmd), Commands::New(ref cmd) => commands::new::execute(cmd, in_test), Commands::Check(ref cmd) => commands::check::execute(cmd), - Commands::Dev(ref cmd) => commands::dev::execute(cmd, &[]), + Commands::Dev(ref cmd) => commands::dev::execute(cmd, &[]).map(|(r, _)| r), Commands::Launch(ref cmd) => commands::launch::execute(cmd), Commands::Build(ref cmd) => commands::build::execute(cmd), Commands::Release(ref cmd) => commands::release::execute(cmd), Commands::Localization(ref cmd) => commands::localization::execute(cmd), Commands::Script(ref cmd) => commands::script::execute(cmd), + Commands::Photoshoot(ref cmd) => commands::photoshoot::execute(cmd), Commands::Utils(ref cmd) => commands::utils::execute(cmd), Commands::Value(ref cmd) => commands::value::execute(cmd), Commands::Wiki(ref cmd) => commands::wiki::execute(cmd), diff --git a/bin/src/modules/mod.rs b/bin/src/modules/mod.rs index bb629f69..65f768b8 100644 --- a/bin/src/modules/mod.rs +++ b/bin/src/modules/mod.rs @@ -19,7 +19,7 @@ pub use file_patching::FilePatching; pub use files::Files; pub use hook::Hooks; pub use new::Licenses; -pub use rapifier::Rapifier; +pub use rapifier::{AddonConfigs, Rapifier}; pub use sign::Sign; pub use sqf::SQFCompiler; pub use stringtables::Stringtables; diff --git a/bin/src/modules/rapifier.rs b/bin/src/modules/rapifier.rs index 25b433c8..1aa77c19 100644 --- a/bin/src/modules/rapifier.rs +++ b/bin/src/modules/rapifier.rs @@ -1,11 +1,18 @@ use std::{ + collections::HashMap, path::PathBuf, - sync::atomic::{AtomicU16, Ordering}, + sync::{ + atomic::{AtomicU16, Ordering}, + RwLock, + }, }; -use hemtt_config::{analyze::lint_check, parse, rapify::Rapify}; +use hemtt_config::{analyze::lint_check, parse, rapify::Rapify, Config}; use hemtt_preprocessor::Processor; -use hemtt_workspace::{addons::Addon, WorkspacePath}; +use hemtt_workspace::{ + addons::{Addon, Location}, + WorkspacePath, +}; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use vfs::VfsFileType; @@ -13,6 +20,17 @@ use crate::{context::Context, error::Error, progress::progress_bar, report::Repo use super::Module; +#[derive(Default)] +pub struct AddonConfigs(RwLock>); + +impl std::ops::Deref for AddonConfigs { + type Target = RwLock>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Default)] pub struct Rapifier; @@ -28,6 +46,7 @@ impl Module for Rapifier { } fn pre_build(&self, ctx: &Context) -> Result { + ctx.state().set(AddonConfigs::default()); let mut report = Report::new(); let counter = AtomicU16::new(0); let glob_options = glob::MatchOptions { @@ -139,6 +158,14 @@ pub fn rapify(addon: &Addon, path: &WorkspacePath, ctx: &Context) -> Result() + .write() + .expect("state is poisoned") + .insert( + (addon.name().to_owned(), *addon.location()), + configreport.config().clone(), + ); } path.with_extension("bin")? } else { diff --git a/bin/src/utils/mod.rs b/bin/src/utils/mod.rs index 550790f5..58467e37 100644 --- a/bin/src/utils/mod.rs +++ b/bin/src/utils/mod.rs @@ -2,5 +2,6 @@ pub mod config; pub mod inspect; pub mod paa; pub mod pbo; +pub mod photoshoot; pub mod sqf; pub mod verify; diff --git a/bin/src/utils/photoshoot.rs b/bin/src/utils/photoshoot.rs new file mode 100644 index 00000000..ff2543f8 --- /dev/null +++ b/bin/src/utils/photoshoot.rs @@ -0,0 +1,63 @@ +use image::{ImageBuffer, Rgb, Rgba}; +use std::path::Path; + +use crate::error::Error; + +pub struct Photoshoot {} + +impl Photoshoot { + pub fn weapon(name: &str, from: &Path) -> Result, Vec>, Error> { + let path = from.join(format!("{name}.png")); + let mut new = image::open(path)?.into_rgba8(); + let crop = 612; + let new = image::imageops::crop(&mut new, (1280 - crop) / 2, 720 - crop, crop, crop); + let mut new = image::imageops::resize( + &new.to_image(), + 512, + 512, + image::imageops::FilterType::Nearest, + ); + for pixel in new.pixels_mut() { + if is_background(*pixel) { + pixel.0[0] = 0; + pixel.0[1] = 0; + pixel.0[2] = 0; + pixel.0[3] = 0; + continue; + } + Self::gamma_rgba(pixel); + } + Ok(new) + } + + pub fn preview(path: &Path) -> Result, Vec>, Error> { + let new = image::open(path)?.into_rgb8(); + let mut new = image::imageops::resize(&new, 455, 256, image::imageops::FilterType::Nearest); + for pixel in new.pixels_mut() { + Self::gamma_rgb(pixel); + } + Ok(new) + } + + // adjust gamma because Arma blasts the hell out of it + fn gamma_rgba(pixel: &mut Rgba) { + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss)] + for i in 0..3 { + pixel.0[i] = ((f32::from(pixel.0[i]) / 255.0).powf(1.8) * 255.0_f32) as u8; + } + } + + fn gamma_rgb(pixel: &mut Rgb) { + #[allow(clippy::cast_possible_truncation)] + #[allow(clippy::cast_sign_loss)] + for i in 0..3 { + pixel.0[i] = ((f32::from(pixel.0[i]) / 255.0).powf(2.2) * 255.0_f32) as u8; + } + } +} + +const fn is_background(pixel: Rgba) -> bool { + (pixel.0[0] >= 240 && pixel.0[1] <= 200 && pixel.0[2] >= 240) + || (pixel.0[0] == 0 && pixel.0[1] >= 30 && pixel.0[2] == 0) +} diff --git a/bin/tests/preset.rs b/bin/tests/preset.rs index 401cd2fe..58567aa3 100644 --- a/bin/tests/preset.rs +++ b/bin/tests/preset.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; -use hemtt::commands::launch::read_preset; +use hemtt::commands::launch::preset; use hemtt_common::arma::dlc::DLC; macro_rules! bootstrap { @@ -22,7 +22,7 @@ fn preset(name: &str, mods: usize, dlc: &[DLC]) { .with_extension("html"); assert!(html.exists(), "Preset not found: {name}"); let html = std::fs::read_to_string(html).unwrap(); - let (preset_mods, preset_dlc) = read_preset(name, &html); + let (preset_mods, preset_dlc) = preset::read(name, &html); assert_eq!(preset_mods.len(), mods); assert_eq!(preset_dlc, dlc); } diff --git a/libs/common/src/arma/control.rs b/libs/common/src/arma/control.rs new file mode 100644 index 00000000..4811f6fe --- /dev/null +++ b/libs/common/src/arma/control.rs @@ -0,0 +1,46 @@ +//! Messages to control Arma from HEMTT + +pub mod toarma { + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize)] + pub enum Message { + Control(Control), + Photoshoot(Photoshoot), + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Control { + Exit, + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Photoshoot { + Weapon(String), + Preview(String), + PreviewRun, + Done, + } +} + +pub mod fromarma { + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize)] + pub enum Message { + Control(Control), + Photoshoot(Photoshoot), + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Control { + Mission(String), + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Photoshoot { + Ready, + Weapon(String), + Previews, + } +} diff --git a/libs/common/src/arma/mod.rs b/libs/common/src/arma/mod.rs index 4bd246c6..5dcb6643 100644 --- a/libs/common/src/arma/mod.rs +++ b/libs/common/src/arma/mod.rs @@ -1,3 +1,4 @@ //! Arma 3 specific functionality. +pub mod control; pub mod dlc; diff --git a/libs/config/src/model/config.rs b/libs/config/src/model/config.rs index 6f76cf26..727ca3cb 100644 --- a/libs/config/src/model/config.rs +++ b/libs/config/src/model/config.rs @@ -2,7 +2,7 @@ use hemtt_common::version::Version; use crate::{analyze::CfgPatch, Class, Number, Property, Value}; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] /// A config file pub struct Config(pub Vec); diff --git a/libs/photoshoot/Cargo.toml b/libs/photoshoot/Cargo.toml new file mode 100644 index 00000000..5c0ce66b --- /dev/null +++ b/libs/photoshoot/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hemtt-photoshoot" +version = "1.0.0" +edition = "2021" +description = "A library for hemtt" +license = "GPL-2.0" + +[lints] +workspace = true diff --git a/libs/photoshoot/src/lib.rs b/libs/photoshoot/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/libs/photoshoot/src/lib.rs @@ -0,0 +1 @@ + diff --git a/libs/photoshoot/src/messages.rs b/libs/photoshoot/src/messages.rs new file mode 100644 index 00000000..5e37f20a --- /dev/null +++ b/libs/photoshoot/src/messages.rs @@ -0,0 +1,41 @@ +pub mod toarma { + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize)] + pub enum Message { + Control(Control), + Photoshoot(Photoshoot), + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Control { + Exit, + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Photoshoot { + Uniform(String), + Done, + } +} + +pub mod fromarma { + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize)] + pub enum Message { + Control(Control), + Photoshoot(Photoshoot), + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Control { + Mission(String), + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Photoshoot { + Ready, + Uniform(String), + } +} diff --git a/libs/workspace/src/addons.rs b/libs/workspace/src/addons.rs index 36f2b65f..f42f8c57 100644 --- a/libs/workspace/src/addons.rs +++ b/libs/workspace/src/addons.rs @@ -168,7 +168,7 @@ impl Addon { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Location { Addons, Optionals,