2023-01-27

Bitwise experimentations

Since I'll have to keep track of consumables withdrawal symptoms, I'd rather check if I can use some bitwise operation. Point is, there's 9 different consumables in core game + 23 additional provided by mod WE3D - Drugs of Night City. Which is actually ideal for storing booleans as bits in Int32.

Here's a sample made quickly on Rust playground, by restricting the usage of bitwise operators to those provided by REDscript.

Applied to REDscript, it should roughly translate to this:

  Redscript
// use enum's variants value as the position in bits inside blackboard's Int32
enum Consumable {
  Invalid = -1,
  Alcohol = 0,
  MaxDOC = 1, // FirstAidWhiff
  BounceBack = 2, // BonesMcCoy
  HealthBooster = 3,
  MemoryBooster = 4,
  OxyBooster = 5,
  StaminaBooster = 6,
  BlackLace = 7,
  // so on and so forth ...
}

@addField(PlayerStateMachineDef)
public let IsWithdrawing: BlackboardID_Int; // this bad boy will contains all consumables withdrawal symptom bool, one for each bit

@addMethod(PlayerPuppet)
public func IsWithdrawing(consumable: Consumable) -> Bool {
  if Equals(EnumInt(consumable), EnumInt(Consumable.Invalid)) { return false; }
  let blackboard: ref<IBlackboard> = this.GetPlayerStateMachineBlackboard();
  let symptoms = blackboard.GetInt(GetAllBlackboardDefs().PlayerStateMachine.IsWithdrawing);
  // get value for nth bit, where n is the consumable value (see enum above) 
  return (symptoms >> EnumInt(consumable)) & 1;
}
public class AddictedSystem extends ScriptableSystem {
  private let player: wref<PlayerPuppet>;
  // ...
  public func SetSymptom(consumable: Consumable, withdrawing: bool) -> Void {
   if Equals(EnumInt(consumable), EnumInt(Consumable.Invalid)) { return; }
   let blackboard: ref<IBlackboard> = this.player.GetPlayerStateMachineBlackboard();
   let before = blackboard.GetInt(GetAllBlackboardDefs().PlayerStateMachine.IsWithdrawing);
   let after = before;
   if withdrawing {
     // set bit to 1
     after |= 1 << flag;
   } else {
     // set bit to 0
     after &= ~(1 << flag);
   }
   if NotEquals(before, after) {
      blackboard.SetInt(GetAllBlackboardDefs().PlayerStateMachine.IsWithdrawing, after);
   }
  }
}

But... wait ! This wouldn't compile, since maybe left shift << and right shift >> might not be supported in REDscript operators. Well, so we're gonna have to do the calculation manually: for example a >> b is equivalent to dividing a by 2 ^ b.

So let's also look at Blackboard supported types. oh... turns out there's a BlackboardID_Uint and BlackboardID_Int ^^

But why look into Rust ? Because Rust and REDscript share inherited similarities (REDscript is built using Rust), and testing in the Rust playground is a bliss, no need to even launch the game !

Well, so here it is, implementing bit flags for BlackboardID_Int:

  Redscript
public func ShiftRight(num: Int32, n: Int32) -> Int32 {
  num / PowI(2, n)
}
public func ShiftLeft(num: Int32, n: Int32) -> Int32 {
  num * PowI(2, n)
}
public func PowI(num: Int32, times: Int32) -> Int32 {
  RoundMath(Cast<Float>(num).PowF(times))
}
public func Invert(num: Int32) -> Int32 {
  let i = 0;
  while i < 32 {
    num = PowI(num, ShiftLeft(1, i));
    i += 1;
  }
  return num;
}

So we would get something like:

Get the nth flag:

  Redscript
return ShiftRight(symptoms, EnumInt(consumable)) & 1;

Set the nth flag:

  Redscript
let after = before; // value from the blackboard
if withdrawing {
  // set bit to 1
  after |= ShiftLeft(1, flag);
} else {
  // set bit to 0
  after &= Invert(ShiftLeft(1, flag));
}

🆕 2023/03/31: Codeware now allows it natively.