Codeware - Documentation
DOWNLOAD CODEWARE
Entities
Components
You can access entity components using new GetComponents()
method:
let player = GetPlayer(GetGameInstance());
let components = player.GetComponents();
for component in components {
LogChannel(n"DEBUG", s"Component: \(component.GetName()) \(component.GetClassName())");
}
You can also add new components:
let comp = new entSkinnedMeshComponent();
comp.mesh *= r"mod\\player\\dynamic.mesh";
comp.meshApperance = n"default";
let player = GetPlayer(GetGameInstance());
player.AddComponent(comp);
To properly initialize components, you have to add components at the right moment, for example, when entity is assembled.
Entity template
You can get path to entity template used to create entity:
let player = GetPlayer(GetGameInstance());
let template = entity.GetTemplatePath();
LogChannel(n"DEBUG", s"Entity Template: \(ResRef.GetHash(template))");
Spawning entities
New entity system allows you to spawn and manage your entities. To create a new entity you have to define an entity spec using next options:
Option | Type | Default | Description |
---|---|---|---|
recordID |
TweakDBID |
– | The record to spawn. Can be any SpawnableObject record. |
templatePath |
ResRef |
– | The entity template (.ent ) to spawn. Can't be used together with recordID . |
appearanceName |
CName |
None |
Initial appearance. If not set, then appearance will be determined based on the record and/or entity template. |
position |
Vector4 |
Zero |
Initial spawn position. |
orientation |
Quaternion |
Identity |
Initial spawn rotation. |
persistState |
Bool |
false |
If true , the entity state will be saved and restored on next game load, otherwise it will be reset to the initial state on every spawn. |
persistSpawn |
Bool |
false |
If true , the entity will be automatically spawned on next game load, otherwise it will exist until the end of the session. |
alwaysSpawned |
Bool |
false |
If true , the entity will be kept always spawned during the game session, otherwise it will only spawn when player is around. |
spawnInView |
Bool |
true |
If true , the entity will spawn even if player sees the spawn position, otherwise it will wait until player will look away. |
active |
Bool |
true |
If true , the entity will spawn on creation, otherwise it will be registered in the system without spawning. |
tags |
array<CName> |
[] |
Initital tags associated with the entity, that can be used to identify individual entities or control group of entities. |
Entities with either persistState
or persistSpawn
option enabled are stored in the save file and can be reused on next game load.
When persistState
is true
, the entity receives persistent ID, that will never change until entity is deleted. You can safely store persistent ID and use it to access the entity. When persistState
is false
, the entity receives temporary ID, that will change on every game load. You can only use temporary ID until the end of the current session. Alternatively you can use tags instead of generated IDs to manage entities.
The next example spawns 3 entities on game load, but only if they weren't spawned before in this playthrough, otherwise entities will be restored in their last saved state.
class MySpawnHandler extends ScriptableSystem {
private let m_entitySystem: wref<DynamicEntitySystem>;
private let m_player: wref<GameObject>;
private let m_handled: Bool;
private func OnAttach() {
// Initialize entity system.
this.m_entitySystem = GameInstance.GetDynamicEntitySystem();
// Register event listener for all entities with MyMod tag.
this.m_entitySystem.RegisterListener(n"MyMod", this, n"OnEntityUpdate");
// At this point player instance is not yet initialized.
// We wait until earliest event when we can access player.
// For new game, it will be OnPlayerAttach callback.
// For loaded save, it will be OnRestored callback.
this.m_handled = false;
}
private func OnRestored(saveVersion: Int32, gameVersion: Int32) {
if !this.m_handled {
this.HandleSpawning();
}
}
private func OnPlayerAttach(request: ref<PlayerAttachRequest>) {
if !this.m_handled {
this.HandleSpawning();
}
}
private cb func OnEntityUpdate(event: ref<DynamicEntityEvent>) {
LogChannel(n"DEBUG", s"Entity \(event.GetEventType()) \(EntityID.GetHash(event.GetEntityID()))");
}
private func HandleSpawning() {
this.m_handled = true;
this.m_player = GetPlayer(this.GetGameInstance());
// Simple check if we're in the main menu world.
if GameInstance.GetSystemRequestsHandler().IsPreGame() {
LogChannel(n"DEBUG", "We're in main menu");
return;
}
// Check if entities with MyMod tag already present in the system.
// Since we spawn persistent entities, they will be restored for a playthrough,
// in which we already spawned our entities once.
if this.m_entitySystem.IsPopulated(n"MyMod") {
LogChannel(n"DEBUG", "Entities restored");
return;
}
// Extra check if there's enough space in front of the player to spawn our entities.
if !this.HasSpaceForSpawning() {
LogChannel(n"DEBUG", "Can't create entities, there's no space");
return;
}
let npcSpec = new DynamicEntitySpec();
npcSpec.recordID = t"Character.spr_animals_bouncer1_ranged1_omaha_mb";
npcSpec.appearanceName = n"random";
npcSpec.position = this.GetPosition(4.0, -45.0);
npcSpec.orientation = this.GetOrientation(-40.0);
npcSpec.persistState = true;
npcSpec.persistSpawn = true;
npcSpec.tags = [n"MyMod"];
let carSpec = new DynamicEntitySpec();
carSpec.recordID = t"Vehicle.v_sport2_quadra_type66";
carSpec.appearanceName = n"quadra_type66__basic_bulleat";
carSpec.position = this.GetPosition(5.5, 0.0);
carSpec.orientation = this.GetOrientation(90.0);
carSpec.persistState = true;
carSpec.persistSpawn = true;
carSpec.tags = [n"MyMod", n"MyMod.Car"];
let deviceSpec = new DynamicEntitySpec();
deviceSpec.templatePath = r"base\\gameplay\\devices\\drop_points\\drop_point.ent";
deviceSpec.position = this.GetPosition(5.0, 45.0);
deviceSpec.orientation = this.GetOrientation(225.0);
deviceSpec.persistState = true;
deviceSpec.persistSpawn = true;
deviceSpec.tags = [n"MyMod"];
this.m_entitySystem.CreateEntity(npcSpec);
this.m_entitySystem.CreateEntity(carSpec);
this.m_entitySystem.CreateEntity(deviceSpec);
LogChannel(n"DEBUG", "Entities created");
}
private func HasSpaceForSpawning() -> Bool {
return !IsEntityInInteriorArea(this.m_player)
&& SpatialQueriesHelper.HasSpaceInFront(this.m_player, 0.1, 10.0, 10.0, 2.0);
}
private func GetDirection(angle: Float) -> Vector4 {
return Vector4.RotateAxis(this.m_player.GetWorldForward(), new Vector4(0, 0, 1, 0),
angle / 180.0 * Pi());
}
private func GetPosition(distance: Float, angle: Float) -> Vector4 {
return this.m_player.GetWorldPosition() + this.GetDirection(angle) * distance;
}
private func GetOrientation(angle: Float) -> Quaternion {
return EulerAngles.ToQuat(Vector4.ToRotation(this.GetDirection(angle)));
}
}
Reference
Vehicles
Unlocking vehicles
Player vehicles can now be enabled by record ID instead of string, which normally can't be retrieved at runtime:
let vehicleSystem = GameInstance.GetVehicleSystem(GetGameInstance());
vehicleSystem.EnablePlayerVehicleID(t"Vehicle.v_sport2_quadra_type66_avenger_player", true);
Lifecycle
Scriptable environments
Environments are singletons that created on scripts initialization and, unlike scriptable systems, live between sessions. To create an environment, you have to define a class derived from ScriptableEnv
.
class MyModEnv extends ScriptableEnv {
private cb func onload() {
LogChannel(n"DEBUG", "Scripts loaded");
}
private cb func OnReload() {
LogChannel(n"DEBUG", "Scripts reloaded");
}
private cb func OnInitialize() {
LogChannel(n"DEBUG", "Game instance initialized, can access game systems");
}
}
Game objects
Be careful with storing references to game objects in static environments. Most game objects must be disposed when game session ends. An unreleased reference can interfere with normal object disposal and lead to bugs such as disappearing sounds.
Game events
Using callback system you can listen to various game events.
Event Name | Event Object Type | Description |
---|---|---|
Entity/Assemble |
EntityLifecycleEvent |
The earliest phase of entity creation process. Entity ID is not assigned yet. |
Entity/Initialize |
EntityLifecycleEvent |
Fired when entity is assembled and ready for use. Entity ID is assigned at this stage. |
Entity/Reassemble |
EntityLifecycleEvent |
Fired when components dynamically added or removed from entity. For example, when you equip items. |
Entity/Attach |
EntityLifecycleEvent |
Fired when entity is added to the world. Can be fired multiple times during entity lifetime. |
Entity/Detach |
EntityLifecycleEvent |
Fired when entity is removed from the world. Can be fired multiple times during entity lifetime. |
Entity/Uninitialize |
EntityLifecycleEvent |
The last stage of entity lifecycle before disposal. |
Session/BeforeStart |
GameSessionEvent |
Fired when a new session starts and game systems begin initialization. |
Session/Start |
GameSessionEvent |
Fired when game systems are initialized, including scriptable systems. |
Session/Ready |
GameSessionEvent |
Fired when session data is loaded and all entities are initialized and spawned. |
Session/BeforeEnd |
GameSessionEvent |
Fired when session is going to end, but all game objects are still available. |
Session/End |
GameSessionEvent |
Fired when game systems begin to dispose game objects and other session data. |
Session/BeforeSave |
GameSessionEvent |
Fired right before saving and is guaranteed to prevent saving until callback is finished. |
Session/AfterSave |
GameSessionEvent |
Fired right after saving. |
Session/Pause |
GameSessionEvent |
Fired when game is paused. |
Session/Resume |
GameSessionEvent |
Fired when game is resumed. |
Input/Key |
KeyInputEvent |
Catches any keyboard and mouse button inputs. |
Input/Axis |
AxisInputEvent |
class MyEntityWatcher extends ScriptableSystem {
private let m_callbackSystem: wref<CallbackSystem>;
private func OnAttach() {
this.m_callbackSystem = GameInstance.GetCallbackSystem();
this.m_callbackSystem.RegisterCallback(n"Entity/Assemble", this, n"OnEntityAssemble");
}
private cb func OnEntityAssemble(event: ref<EntityLifecycleEvent>) {
let entity = event.GetEntity();
if entity.GetTemplatePath() == r"base\\characters\\entities\\player\\player_ma_fpp.ent" {
let puppet = entity as PlayerPuppet;
puppet.AddComponent(new MyCustomComponent());
puppet.AddTag(n"MyCustomTag");
this.m_callbackSystem.UnregisterCallback(n"Entity/Assemble", this);
}
}
}
By default callbacks are auto removed at the end of the session. But you can register a callback that will stick between sessions by passing 3rd parameter sticky = true
.
class MyModEnv extends ScriptableEnv {
private let m_callbackSystem: wref<CallbackSystem>;
private cb func onload() {
this.m_callbackSystem = GameInstance.GetCallbackSystem();
this.m_callbackSystem.RegisterCallback(n"Session/Ready", this, n"OnSessionReady", true);
this.m_callbackSystem.RegisterCallback(n"Input/Key", this, n"OnKeyInput", true);
}
private cb func OnSessionReady(event: ref<GameSessionEvent>) {
LogChannel(n"DEBUG", "All systems ready");
}
private cb func OnKeyInput(event: ref<KeyInputEvent>) {
LogChannel(n"DEBUG", s"Input: \(event.GetAction()) \(event.GetKey())");
}
}
Unlike other game systems, callback system can be accessed before game instance is initialized.
Reference
Widgets
Layers and windows
All UI layers and windows can now be accessed, which gives direct access to all existing widgets, game and logic controllers.
let inkSystem = GameInstance.GetInkSystem();
let layers = inkSystem.GetLayers();
for layer in layers {
LogChannel(n"DEBUG", s"UI Layer: \(layer.GetLayerName()) \(layer.GetGameController().GetClassName())");
}
You can list all currently spawned game controllers:
let inkSystem = GameInstance.GetInkSystem();
let layers = inkSystem.GetLayers();
for layer in layers {
for controller in layer.GetGameControllers() {
LogChannel(n"DEBUG", s"Game Controller: \(controller.GetClassName())");
}
}
You can also access a layer directly by its name:
let inkSystem = GameInstance.GetInkSystem();
let hudRoot = inkSystem.GetLayer(n"inkHUDLayer").GetVirtualWindow();
let hello = new inkText();
hello.SetText("HELLO HUD");
hello.SetFontFamily("base\\gameplay\\gui\\fonts\\orbitron\\orbitron.inkfontfamily");
hello.SetFontStyle(n"Bold");
hello.SetFontSize(200);
hello.SetTintColor(new HDRColor(1.1761, 0.3809, 0.3476, 1.0));
hello.SetAnchor(inkEAnchor.Centered);
hello.SetAnchorPoint(0.5, 0.5);
hello.Reparent(hudRoot);
Spawning widgets
When spawning widgets you can attach any custom game or logic controller on the fly. If a library item defines controller, it will be overridden by your controller. All controller properties set by the library item are inherited.
To override the controller you have to specify the controller class name next to the library item name:
let btn = this.SpawnFromLocal(parent, n"HyperlinkButton:MyButtonController");
When widget is spawning the controller will be overriden.
public class MyButtonController extends MenuItemController {
protected cb func OnInitialize() -> Bool {
super.OnInitialize();
let dаta: MenuData;
data.label = "My Button";
data.icon = n"ico_deck_hub";
this.Init(data);
}
}
Controller overrides also work inside .inkwidget
library. When a widget requires a library item, for example, inkVirtualGridController
, you can also insert the controller into the library item name: itemDisplay
→ itemDisplay:MyMod.MyController
.
Scripted widgets
Widgets and controllers created from scripts are now attached to all game systems and fully functional. You can initialize any native controller, e.g. inkScrollController
, or create your own.
The new inkComponent
class allows you to define the widget and logic as one unit:
public class MyComponent extends inkComponent {
// Called when component is created
// Must return the root widget of the component
protected cb func OnCreate() -> ref<inkWidget> {
let root = new inkRectangle();
root.SetAnchor(inkEAnchor.Fill);
root.SetOpacity(1.0);
return root;
}
// Called when component is attached to the widget tree
protected cb func OnInitialize() {}
// Called when component is no longer used anywhere
protected cb func OnUninitialize() {}
public func SetColor(color: HDRColor) {
this.GetRootWidget().SetTintColor(color);
}
}
Then it can be added to an existing widget tree:
let comp = new MyComponent();
comp.SetColor(new HDRColor(1.1761, 0.3809, 0.3476, 1.0));
comp.Reparent(parent);
Backwards compatibility
Existing mods can keep usinginkCustomController
without changes.
UI Framework
Codeware includes ready-to-use components for buttons, action hints, text inputs, popups. For examples of use, see the following mods:
Reference
Localization
Localization system
Using localization system you can:
- Translate your mod with scripts and no other dependencies
- Automatically apply translations based on the game language settings
- Use different language packages for interface and subtitles
- Vary translations based on player gender
To access translations you need an instance of the system:
module MyMod.UI
import Codeware.UI.*
import Codeware.Localization.*
public class MyModWidget extends inkCustomController {
private let m_title: ref<inkText>;
private let m_action: ref<inkText>;
private let m_translator: ref<LocalizationSystem>;
private cb func OnInitialize() -> Void {
this.m_title.SetText(this.GetLocalizedText("MyMod-Title"));
this.m_action.SetText(this.GetLocalizedText("MyMod-Action-Use"));
}
private func GetLocalizedText(key: String) -> String {
if !IsDefined(this.m_translator) {
this.m_translator = LocalizationSystem.GetInstance(this.GetGame());
}
return this.m_translator.GetText(key);
}
}
Localization providers
First, you need to define a localization provider. The main purpose of the provider is to resolve packages by language code and define fallback language:
module MyMod.Localization
import Codeware.Localization.*
public class LocalizationProvider extends ModLocalizationProvider {
public func GetPackage(language: CName) -> ref<ModLocalizationPackage> {
switch language {
case n"en-us": return new English();
case n"de-de": return new German();
case n"it-it": return new Italian();
default: return null;
}
}
public func GetFallback() -> CName {
return n"en-us";
}
}
Provider can also track locale changes and notify other components:
public class LocalizationProvider extends ModLocalizationProvider {
public func OnLocaleChange() -> Void {
// Notify other components, clear cache, ...
}
}
Localization packages
Second, you define localization packages that contain translations for a specific language:
module MyMod.Localization
import Codeware.Localization.*
public class English extends ModLocalizationPackage {
protected func DefineTexts() -> Void {
this.Text("MyMod-Title", "Best Mod");
this.Text("MyMod-Action-Use", "Do It");
// To define gender specific translations:
this.TextM("MyMod-Greeting", "Hey Boy");
this.TextF("MyMod-Greeting", "Hey Girl");
// Alternatively it can be one statement:
this.Text("MyMod-Greeting", "Hey Girl", "Hey Boy");
}
protected func DefineSubtitles() -> Void {
// this.Subtitle(...);
// this.SubtitleM(...);
// this.SubtitleF(...);
}
}
Third-party translations
Translations can also be provided by mod users in the form of another mod:
module MyMod_Chinese
import Codeware.Localization.*
public class LocalizationProvider extends ModLocalizationProvider {
public func GetPackage(language: CName) -> ref<ModLocalizationPackage> {
return Equals(language, n"zh-cn") ? new LocalizationPackage() : null;
}
}
public class LocalizationPackage extends ModLocalizationPackage {
protected func DefineTexts() -> Void {
this.Text("MyMod-Title", "最佳模组");
this.Text("MyMod-Action-Use", "做吧");
}
}
Reference
Resources
Resource references
Resource references (rRef
and raRef
) can now be imported and manipulated from scripts.
@addField(WeaponObject)
native let effect: ResourceRef;
@addField(inkMask)
native let textureAtlas: ResourceAsyncRef;
Reference can be initialized by assigning a resource path using *=
operator:
let weapon = new WeaponObject();
weapon.effect *= r"base\\gameplay\\game_effects\\strongmelee.es";
LogChannel(n"DEBUG", s"Hash: \(ResourceRef.GetHash(weapon.effect))");
LogChannel(n"DEBUG", s"Loaded: \(ResourceRef.IsLoaded(weapon.effect))");
Checking resource existence
With basic depot access, you can check if any particular archive (legacy and redmod) or resource is present in the game.
For example, you can use to check if mod is intalled correctly:
let depot = GameInstance.GetResourceDepot();
if !depot.ArchiveExists("MyMod.archive") {
LogChannel(n"DEBUG", "MyMod.archive not found. Did you enable mods?");
}
Or to handle compatibility with another mod:
let depot = GameInstance.GetResourceDepot();
if depot.ResourceExists(r"mod\\entities\\vehicle.ent") {
LogChannel(n"DEBUG", "Another mod detected");
}
Reading resources
Currently reading resources is only available for Cyber Engine Tweaks.
local listener
local waiting = {}
registerForEvent('onInit', function()
listener = NewProxy({
OnResourceReady = {
args = {'whandle:ResourceToken'},
callback = function(token)
local template = token:GetResource()
for _, appearance in ipairs(template.appearances) do
print(NameToString(appearance.name))
end
waiting[token:GetHash()] = nil
end
}
})
local path = [[base\vehicles\standard\v_standard3_thorton_mackinaw_larimore_01.ent]]
local token = Game.GetResourceDepot():LoadResource(path)
if not token:IsFailed() then
token:RegisterCallback(listener:Target(), listener:Function('OnResourceReady'))
waiting[token:GetHash()] = token
end
end)
Reference
Reflection
Inspecting types and values
You can get various type information using type name or variable:
let cls = Reflection.GetClass(n"inkWidget");
if cls.IsNative() {
LogChannel(n"DEBUG", s"\(cls.GetName()) is native class");
}
for prop in cls.GetProperties() {
LogChannel(n"DEBUG", s"\(prop.GetName()): \(prop.GetType().GetName())");
}
You can also access properties and call methods:
let image = new inkImage();
image.SetTexturePart(n"foo");
let imageRef = Reflection.GetTypeOf(image); // Resolves to ref<inkImage>
let imageClass = imageRef.GetInnerType().AsClass(); // Get to inkImage class
let prop = imageClass.GetProperty(n"texturePart");
let getter = imageClass.GetFunction(n"GetTexturePart");
let setter = imageClass.GetFunction(n"SetTexturePart");
let val1 = prop.GetValue(image); // Read property from instance
let val2 = getter.Call(image); // Call getter on instance
LogChannel(n"DEBUG", s"Expected type: \(prop.GetType().GetName())");
LogChannel(n"DEBUG", s"Value from property: \(FromVariant<CName>(val1))");
LogChannel(n"DEBUG", s"Value from getter: \(FromVariant<CName>(val2))");
setter.Call(image, [n"bar"]); // Change the value using setter
LogChannel(n"DEBUG", s"New value: \(image.GetTexturePart())");
Custom callbacks
You can implement your own callbacks similar to game native callbacks:
struct MyCallback {
let target: wref<IScriptable>;
let function: CName;
}
class MyService {
private let callbacks: array<MyCallback>;
public func RegisterCallback(target: ref<IScriptable>, function: CName) {
ArrayPush(this.callbacks, new MyCallback(target, function));
}
public func FireCallbacks(data: ref<IScriptable>) {
for callback in this.callbacks {
if IsDefined(callback.target) {
Reflection.GetClassOf(callback.target)
.GetFunction(callback.function)
.Call(callback.target, [data]);
}
}
}
}
You can also handle call status and return value:
class MyService {
public func FireCallback(target: ref<IScriptable>, function: CName, data: ref<IScriptable>) {
let type = Reflection.GetClassOf(target);
let callable = type.GetFunction(function);
if IsDefined(callable) {
let status = false;
let result = callable.Call(target, [data], status);
if status {
LogChannel(n"DEBUG", s"Result: \(VariantTypeName(result))");
} else {
LogChannel(n"DEBUG", s"Method \(type.GetName()).\(function) must accept one parameter");
}
} else {
LogChannel(n"DEBUG", s"Method \(type.GetName()).\(function) not found");
}
}
}
Don't forget to use cb
modifier for callbacks to be able to use functions short names.
class MyHandler {
protected cb func Callback(data: ref<IScriptable>) {
LogChannel(n"DEBUG", s"It's working! Received \(data.GetClassName()).");
}
}
let handler = new MyHandler();
let service = new MyService();
service.RegisterCallback(handler, n"Callback");
service.FireCallbacks(new GameObject());
Mods and patches compatibility
In cases where you can't use conditional compilation with ModuleExists()
, you can use reflection to check if a particular class, property or function exists, apply different logic and access it safely.
Reference
Utilities
CName
let name = n"test";
let hash = NameToHash(name);
LogChannel(n"DEBUG", s"Name: \(name) -> Hash: \(hash)");
NodeRef
let nodeRef = CreateNodeRef("$/my/new/#node");
LogChannel(n"DEBUG", s"NodeRef: \(NodeRefToHash(nodeRef))");
CRUID
let id = CreateCRUID(1337ul);
LogChannel(n"DEBUG", s"CRUID: \(CRUIDToHash(id))");
Hash functions
let hash1: Uint64 = FNV1a64("data");
let hash2: Uint32 = FNV1a32("data");
let hash3: Uint32 = Murmur3("data");
Bitwise operations
let mask1: Uint64 = BitSet64(0, 4, true);
let mask2: Uint16 = BitShiftL16(1, 3);
let test: Bool = BitTest32(6, 1);