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:
// 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
^^
- u32, see Rust playground
- i32, see Rust playground
- u64, on latest Rust playground
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
:
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:
return ShiftRight(symptoms, EnumInt(consumable)) & 1;
Set the nth flag:
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.