Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/simonvic/sframework
sFramework is a set of tools and utilities mainly used by the sUDE mods; it could be considered the core of the sUDE project
https://github.com/simonvic/sframework
Last synced: 16 days ago
JSON representation
sFramework is a set of tools and utilities mainly used by the sUDE mods; it could be considered the core of the sUDE project
- Host: GitHub
- URL: https://github.com/simonvic/sframework
- Owner: simonvic
- License: mit
- Created: 2021-08-15T16:57:19.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-02-20T13:47:46.000Z (9 months ago)
- Last Synced: 2024-02-21T14:57:21.948Z (9 months ago)
- Language: C
- Homepage:
- Size: 656 KB
- Stars: 6
- Watchers: 2
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# sFramework
# Getting started
sFramework is the core of the sUDE project.
It ships many features and utilities used and/or implemented by sUDE modules :
- [Advanced PostProcessing](#PostProcessing-effects)
- [Cameras Overlays](#Camera-Overlays)
- [Game and User configuration interface](#Configurations-interfaces)
- [Helper classes and utilities for developing and debugging](#Utilities)
- Improvements to base game classes
- ...more---
---
PostProcessing effects
sFramework ships a centralized post processing effects manager, with the goal of allowing multiple requests of the same effects, without hardcoding them.
## SPPEffect
`SPPEffect` is the "container" of any PostProcess Effect you wish to add to it (e.g. saturation, vignette, motion blur etc.).
```csharp
SPPEffect myPPE = new SPPEffect();
```To add a parameter use the provided setters:
```csharp
myPPE.setVignette(intensity, color);
myPPE.setRadialBlur(powerX, powerY, offsetX, offsetY);
myPPE.setChromAber(powerX, powerY);
//...
```To apply it, "hand it over" to the SPPEManager, which will calculate the correct value of all active SPPEffect and then apply it
```csharp
SPPEManager.activate(myPPE);
```and to deactivate it:
```csharp
SPPEManager.deactivate(myPPE);
```
## SPPEffectAnimated
A `SPPEffectAnimated` is just like a `SPPEffect`, but it has an animation mechanism which allows you to animate the values of a PostProcess effect.
A `SPPEffectAnimated` is an *abstract* class. You need to implement it with your own class and override the `onAnimate()` method, which will be called on every frame.
There also is a timed variant `SPPEffectTimed`, which will be automatically deactivated once a certain amount has passed.
To create your animation, simply extend either `SPPEffectAnimated` or `SPPEffectTimed`
```csharp
class MyLoopAnimation : PPELoopedParams{
override void onAnimate(float deltaTime){
/* change PPE values here
setOverlay(...);
setChromAber(...);
setCameraEffects(...);
*/
setVignetteIntensity( Math.Sin(getTime()) );
}
}class MyTimedAnimation : SPPEffectTimed{
override void onAnimate(float deltaTime){
setVignetteIntensity( Math.Cos(getTime()) );
}
}
```A `SPPEffectTimed` also has a "duration" which can be set with the constructor, or the provided method:
```csharp
MyTimedAnimation myTimedAnimation = new MyTimedAnimation(6); // the animation will last 6 seconds
myTimedAnimation.setDuration(10.0); // the animation will last 10 seconds
```The activation of the animation is identical to any other `SPPEffect`
```csharp
MyLoopAnimation myAnimation = new MyLoopAnimation();
SPPEManager.activate(myAnimation);MyTimedAnimation myTimedAnimation = new MyTimedAnimation(5.5);
SPPEManager.activate(myTimedAnimation);
```If you want to manually manage the animation you can use the provided methods
```csharp
myAnimation.start(); // Set the animation state to "Playing"
myAnimation.stop(); // Reset the time and set the animation state to "Stopped"
myAnimation.pause(); // Freeze the animation values and set the animation state to "Paused"
myAnimation.resume(); // Resume the the animation and set the animation state to "Playing"
```## The insides of SPPEManager
PostProcess Effect Manager
The SPPEManager is in charge of managing the PostProcessing effects; this is a small diagram roughly showing how it works
---
---
Camera Overlays
A camera overlay is nothing else than an image, used like an HUD.
The fundemental unit of camera overlays is the `SCameraOverlay`, a very simple wrapper for the `ImageWidget` (the DayZ UI component that holds an image).It can be used in countless ways:
As an animated UI :
or for emulating headgear damage:
##### (*from sVisual, MotoHelmet in various health state: Pristine, Worn, Damaged, BadlyDamaged and Ruined)*
Defining an overlay is very simple and very similar to SPPEffects, in fact there are three types as well and the logic is identical to the SPPEffects:
- `SCameraOverlay`
- `SCameraOverlayAnimated`
- `SCameraOverlayTimed````csharp
class MyAnimatedOverlay : SCameraOverlayAnimated {override void onInit(){
setImage("path/to/texture.edds");
//...
}//onAnimate() gets called every frame!
override void onAnimate(float deltaTime){
setSize(Math.Sin(getTime()));
//setPosition(...)
//setRotation(...)
//setMask(...)
//...
}
}
```To activate/deactivate an overlay, you use the `SCameraOverlayManager`:
```csharp
// NOTE: SCameraOverlaysManager is a singleton
SCameraOverlaysManager.getInstance().activate(myOverlay);
```## Clothing overlays
sFramework is capable of automatically activating/deactivating overlays when a clothing item is equipped/unequipped; making use of this feature is super easy. You just need to define a list of overlays inside your clothing item in your `config.cpp` as follows:
```cpp
class YOUR_CLOTHING_ITEM_CLASSNAME{
class sUDE {
class CameraOverlays {
class overlay_0 : SCameraOverlay {
image="path/to/your/image/pristine.edds";
};
class overlay_1 : SCameraOverlay {
image="path/to/your/image/worn.edds";
};
class overlay_2 : SCameraOverlay {
image="path/to/your/image/damaged.edds";
};
class overlay_3 : SCameraOverlay {
image="path/to/your/image/badlydamaged.edds";
};
/*
class overlay_X : SCameraOverlay {
image="path/to/your/image/xxx.edds";
};
*/
};
};
};
```A `SCameraOverlay` has many attributes you can play with, which can be set either by scripts or in the config.
Currently available attributes are:```cpp
image=""; // Resource image path, can be whatever an ImageWidget accepts texture
alpha=1.0; // [0.0 - 1.0] Alpha value (transparency)
mask=""; // Resource image path, can be whatever an ImageWidget accepts as mask
maskProgress=1.0; // [0.0 - 1.0] Mask progress
maskTransitionWidth=1.0; // Mask transition width (used as progress + transitionWidth)
position[] = {0.0, 0.0}; // [0.0 - 1.0] X and Y position in screenspace
size[] = {1.0, 1.0}; // [0.0 - 1.0] X and Y size in screenspace
rotation[] = {0.0, 0.0, 0.0}; // Yaw, Pitch and Roll defined in degrees
priority = 0; // Higher priority means closer to the camera (also known as z-depth)
targetCameras[] = {"DayZPlayerCamera"}; // Camera typename on which the overlay will be visible
hidesWithIngameHUD = 0; // [0 = false, 1 = true] Determines if it must hides when the player hides the ingame HUD
```
# Configurations interfaces
## SUserConfig
`SUserConfig` has the purpose to help in creating user (client) settings in just few lines of code.
Implement the `SUserConfigBase` as follows:
```csharp
class MySUserConfig : SUserConfigBase {/**
* Where the config will be saved
*/
override string getPath(){
return "$saves:\\path\\to\\my\\config.json";
}
/**
* Where the config with default values will be saved
*/
override string getDefaultPath(){
return "$profile:\\path\\to\\my\\config_default.json";
}/**
* Implement the deserialization
*/
override void deserialize(string data, out string error){
auto cfg = this;
m_serializer.ReadFromString(cfg, data, error);
}
/**
* Implement the serialization
*/
override string serialize() {
string result;
auto cfg = this;
getSerializer().WriteToString(cfg, true, result);
return result;
}
override string serializeDefault() {
string result;
auto cfg = new MySUserConfig();
getSerializer().WriteToString(cfg, true, result);
return result;
}// Configuration options (and their default values) you want to store
float myFloatOption = 0.69;
//bool myBoolOption = true;
//int myIntOption = 69;
//ref array myarrayOption = {0.69, 42.0, 420.69, 0.42069};
//any other optionsoverride void registerOptions() {
super.registerOptions();
registerOption("myFloatOption", new SUserConfigOption(myFloatOption));
// registerOption("myBoolOption", new SUserConfigOption(myBoolOption));
// registerOption("myIntOption", new SUserConfigOption(myIntOption));
// registerOption("myarrayOption", new SUserConfigOptionArray(myarrayOption));
// any other option
}
}
```you can now save it, load it and more with few lines
```csharp
MyUserConfig myCfg = new MyUserConfig();
myCfg.load();
myCfg.save();
//myCfg.isValid()
// etc.
```Making a user interface for changing those option is very easy too.
```csharp
class MyOptionsMenu : SOptionsMenuBase {
override string getName() {
return "MyOptionsName";
}
override string getLayout() {
return "path/to/interface.layout";
}
ref SliderWidget myFloatOptionSlider;
ref CheckBoxWidget myBoolOptionCheckbox;
override void onInit() {
super.onInit();
setUserConfig(instanceOfYourConfig());
}
override void onBuild() {
super.onBuild();
// Widget to link name of widget in your layout option to link
initOptionWidget(myFloatOptionSlider, "myFloatOption", getUserConfig().getOptionFloat("myFloatOption"));
initOptionWidget(myBoolOptionCheckbox, "myBoolOption", getUserConfig().getOptionBool("myBoolOption"));
}
}
```## SGameConfig
`SGameConfig` contains just a set of utilities to read the game `config.cpp` more easily
# sTest (UnitTesting framework)
`sFramework` also ships `sTest`, a UnitTesting framework for Enforce scripts, based on industry standard frameworks such as JUnit for Java.
Test units are super simple to define and use:
1. Create a TestUnit class (extends `STestUnit`)
2. Create some test cases function
3. Register the test cases by passing the test case name (function name) to `registerTestCases` method```csharp
class MyTestUnit : STestUnit {
override void init() {
registerTestCases({
"testThisFeature",
"testThisOtherFeature",
"shouldFail"
});
}void testThisFeature() {
// do something...
// assert something
assertEqual(10, 5 + 5);
}void testThisOtherFeature() {
// do something...
// assert something
assertTrue(true);
}void shouldFail() {
// this test case will fail!
Class someClass = null;
assertNotNull(someClass);
}
}
```You can now run the test unit by passing its name to `sTest`:
TIP: you can execute the following in the workbench console
```csharp
STest.run(MyTestUnit);// optionally an array of test units can be used to run multiple test units
STest.run({MyTestUnit, MyOtherTestUnit, MyLastTestUnit});
```The result of the tests can be seen in the output window of the workbench or inside sUDE logs.
```text
=======================================================================
Running tests...
-----------------------------------------------------------------------
MyTestUnit
│ ├ [ ✓ ] PASSED - testThisFeature
│ ├ [ ✓ ] PASSED - testThisOtherFeature
│ ├ [ × ] FAILED - shouldFail
│ │ ├ Expected: true
│ │ ├ Actual: false
-----------------------------------------------------------------------
PASSED | FAILED | SKIPPED
2 1 0
=======================================================================
```You can decide not to stop when a test fails:
```csharp
STest.shouldContinueAtFail = true; // default: false
```or to change verbosity in logging:
```csharp
STest.verbosity = 3; // default: 1
```
## Assertions
You have access to multiple assertions:
- `assertEqual(x, y)` with x and y of type `float`, `int`, `string`, `bool`, `array`
- `assertTrue(x)` with x of type `bool`
- `assertFalse(x)` with x of type `bool`
- `assertNull(x)` with x of type `Class`
- `assertNotNull(x)` with x of type `Class`## Advanced TestUnit usage
If you need to perform some actions before or after each test unit or test case you can define and register some callbacks:
```csharp
class MyTestUnit : STestUnit {
override void init() {registerBeforeClassCallbacks({
"doSomethingBeforeTestUnit"
});
registerBeforeCallbacks({
"doSomethingBeforeEachTestCase"
});registerAfterCallbacks({
"doSomethingAfterEachTestCase"
});registerAfterClassCallbacks({
"doSomethingAfterTestUnit"
});// registerTestCases({
// ...
// });
}void doSomethingBeforeTestUnit() {
// do something ...
}void doSomethingBeforeEachTestCase() {
// do something ...
}void doSomethingAfterEachTestCase() {
// do something ...
}void doSomethingAfterTestUnit() {
// do something ...
}}
```If you need to write some more complext test cases, you can also manually `fail()`, `pass()` or `skip()`. Example:
```csharp
void testSomethingComplex() {
int x = 2;
int y = 2;
int actual = x + y;
int expected = 4;if ( x == y) {
fail("x and y not equal", "x and y are equal", "Failed during X and Y comparison");
} else {
assertEqual(expected, actual);
}
}
```
# Utilities
## SDebugUI
A fully featured API for quick creation of debug intefaces.
```csharp
class SomeClass {
void OnUpdate(float timeslice) {
auto dui = SDebugUI.of("TestDebugUI");
dui.begin();
dui.window("Debug monitor");
dui.text("Day Time : " + GetGame().GetDayTime());
dui.newline();
dui.textrich(" ");
dui.textrich("You can click on the slider, or you can use the mouse wheel");
dui.textrich("If you hold shift while using mouse wheel, it will go wrooom!");
float sliderValue;
dui.slider("mySlider", sliderValue);
dui.textrich("The value of sliderValue is: "+ sliderValue +"");
bool checkValue
dui.check("myCheck", checkValue);
dui.text("CheckValue: " + checkValue);
dui.button("click me", this, "printSum", new Param2(69, 420));
dui.newline();
dui.table({
{"Attribute", "Value"},
{"Time", ""+GetGame().GetTickTime()},
{"Radio volume", ""+GetGame().GetSoundScene().GetRadioVolume()},
{"VoIP volume", ""+GetGame().GetSoundScene().GetVOIPVolume()},
{"VoIP level", ""+GetGame().GetSoundScene().GetAudioLevel()}
});
dui.plotlive("Sin", Easing.EaseInBounce(Math.AbsFloat(Math.Sin(m_time))));
dui.end();
}void printSum(int x, int y) {
Print(x + y);
}
}
```## SColor
`SColor` helps you defining and using colors. A few examples:
```csharp
//hex values, like in CSS
SColor.rgb(0xFF0000); //red
SColor.rgba(0xFF000055); //red slightly transparent
SColor.argb(0x55FF0000); //red slightly transparent//separated rgb channels
SColor.rgb(60, 97, 178); //blueish
SColor.rgba(60, 97, 178, 0); //blueish
SColor.argb(0, 60, 97, 178); //blueish// hue saturation and brightness
SColor.hsb(0.60, 0.65, 0.87); //yellowish// presets (taken from https://www.w3schools.com/cssref/css_colors.asp)
SColor.rgb(RGBColors.RED);
SColor.rgb(RGBColors.AQUAMARINE);
SColor.rgb(RGBColors.YELLOW_GREEN);
```## SObservableArray
A list that allows listeners to track changes when they occur.
```csharp
class MyClass {
ref SObservableArray observableArray = new SObservableArray();void MyClass() {
observableArray.addOnChangeListener(new SArrayChangeListener(this, "onChange"));// multiple listeners can be added
observableArray.addOnInsertListener(new SArrayInsertListener(this, "onInsert"));
observableArray.addOnInsertListener(new SArrayInsertListener(this, "onInsert2"));observableArray.addOnPreRemoveListener(new SArrayPreRemoveListener(this, "onPreRemove"));
observableArray.addOnClearListener(new SArrayClearListener(this, "onClear"));
}void onChange() {
SLog.d("Array has changed");
}void onInsert(int value, int position) {
SLog.d("Value " + value + " has been inserted in position " + position);
}void onInsert2(int value, int position) {
// do somehting...
}void onPreRemove(int indexToBeRemoved) {
SLog.d("Index " + indexToBeRemoved + " will be removed");
}void onClear() {
SLog.d("Array has been cleared");
}}
``````csharp
observableArray.insert(69); // onChange, onInsert and onInsert2 will be called
observableArray.insert(420); // onChange, onInsert and onInsert2 will be calledobservableArray.removeItem(69); // onPreRemove and onChange will be called
observableArray.remove(0); // onPreRemove and onChange will be called
observableArray.clear(); // onClear and onChange will be called
```## SSpawnable
`SSpawnable` helps you in quickly spawn items with a lot of attachments:
```csharp
// Build an M4A1 with multiple attachments
SSpawnable m4 = SSpawnable.build("M4A1").withAttachments({
"M4_Suppressor",
"M4_OEBttstck",
"M4_RISHndgrd"
});// Build an M16A2 with no attachments
SSpawnable m16 = SSpawnable.build("M16A2");// Build an AK101 with multiple attachments (and they attachments too)
SSpawnable ak = SSpawnable.build("AK101").withAttachments({
"AK_Suppressor",
"AK_PlasticBttstck",
"AK_RailHndgrd"
}).withSpawnableAttachments(
(new SSpawnable("PSO11Optic")).withAttachment("Battery9V"),
(new SSpawnable("UniversalLight")).withAttachment("Battery9V"));// Actually spawn the items
m4.spawn(position);
m16.spawn(position);
ak.spawn(position);
```## SRaycast
`SRaycast` helps you launching raycasts with more flexibility:
```csharp
SRaycast ray = new SRaycast(/**...*/);
vector contactPositon = ray
.from(thisPosition)
.to(thisOtherPosition)
.ignore(thisItem, thisOtherItem)
.launch()
.getContactPosition();if (ray.hasHit()){
SLog.d("Raycast has hit at this position" + contactPositon);
}
```## SFlagOperator
`SFlagOperator` helps you in bitwise operations, especially when working with flags, hence the name.
```csharp
enum MyFlags {
A = 1,
B = 2,
C = 4,
D = 8,
E = 16
}SFlagOperator fop = new SFlagOperator(MyFlags.A | MyFlags.C);
SLog.d("Result : " + fop.collectBinaryString());
// Result : 0000 00101fop.set(MyFlags.B);
fop.reset(MyFlags.A)
SLog.d("Result : " + fop.collectBinaryString());
// Result : 0000 00110SLog.d("A is set : " + fop.check(MyFlags.A));
//A is set : falseSLog.d("B is set : " + fop.check(MyFlags.B));
//B is set : true
```
# Contact me
Found a bug or want to give a suggestion? Feel free to contact me!
---
Buy me a coffee :)