Create an account

Codeware - Documentation

Upload: 19 Apr 2023, 11:24
Created by: psiberx
Uploaded by: Aqxaro

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 using inkCustomController 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);

Reference

Comments
The minimum comment length is 10 characters.