Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

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

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 options

override 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 called

observableArray.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 00101

fop.set(MyFlags.B);
fop.reset(MyFlags.A)
SLog.d("Result : " + fop.collectBinaryString());
// Result : 0000 00110

SLog.d("A is set : " + fop.check(MyFlags.A));
//A is set : false

SLog.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 :)