sFramework is the core of the sUDE project.
It ships many features and utilities used and/or implemented by sUDE modules :
- Advanced PostProcessing
- Cameras Overlays
- Game and User configuration interface
- Helper classes and utilities for developing and debugging
- Improvements to base game classes
- ...more
sFramework ships a centralized post processing effects manager, with the goal of allowing multiple requests of the same effects, without hardcoding them.
SPPEffect
is the "container" of any PostProcess Effect you wish to add to it (e.g. saturation, vignette, motion blur etc.).
SPPEffect myPPE = new SPPEffect();
To add a parameter use the provided setters:
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
SPPEManager.activate(myPPE);
and to deactivate it:
SPPEManager.deactivate(myPPE);
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
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:
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
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
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"
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
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
:
// NOTE: SCameraOverlaysManager is a singleton
SCameraOverlaysManager.getInstance().activate(myOverlay);
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:
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:
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
SUserConfig
has the purpose to help in creating user (client) settings in just few lines of code.
Implement the SUserConfigBase
as follows:
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<float> myarrayOption = {0.69, 42.0, 420.69, 0.42069};
//any other options
override void registerOptions() {
super.registerOptions();
registerOption("myFloatOption", new SUserConfigOption<float>(myFloatOption));
// registerOption("myBoolOption", new SUserConfigOption<bool>(myBoolOption));
// registerOption("myIntOption", new SUserConfigOption<int>(myIntOption));
// registerOption("myarrayOption", new SUserConfigOptionArray<float>(myarrayOption));
// any other option
}
}
you can now save it, load it and more with few lines
MyUserConfig myCfg = new MyUserConfig();
myCfg.load();
myCfg.save();
//myCfg.isValid()
// etc.
Making a user interface for changing those option is very easy too.
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
contains just a set of utilities to read the game config.cpp
more easily
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:
- Create a TestUnit class (extends
STestUnit
) - Create some test cases function
- Register the test cases by passing the test case name (function name) to
registerTestCases
method
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
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.
=======================================================================
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:
STest.shouldContinueAtFail = true; // default: false
or to change verbosity in logging:
STest.verbosity = 3; // default: 1
You have access to multiple assertions:
assertEqual(x, y)
with x and y of typefloat
,int
,string
,bool
,array<float>
assertTrue(x)
with x of typebool
assertFalse(x)
with x of typebool
assertNull(x)
with x of typeClass
assertNotNull(x)
with x of typeClass
If you need to perform some actions before or after each test unit or test case you can define and register some callbacks:
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:
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);
}
}
A fully featured API for quick creation of debug intefaces.
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("<image set='dayz_gui' name='icon_pin' /> ");
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 <font name='gui/fonts/amorserifpro'>sliderValue</font> is: <b>"+ sliderValue +"</b>");
bool checkValue
dui.check("myCheck", checkValue);
dui.text("CheckValue: " + checkValue);
dui.button("click me", this, "printSum", new Param2<int,int>(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
helps you defining and using colors. A few examples:
//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);
A list that allows listeners to track changes when they occur.
class MyClass {
ref SObservableArray<int> observableArray = new SObservableArray<int>();
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");
}
}
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
helps you in quickly spawn items with a lot of attachments:
// 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
helps you launching raycasts with more flexibility:
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
helps you in bitwise operations, especially when working with flags, hence the name.
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